We are trying to setup a top level global unhadled error detector on our hybrid Ionic 1 app. Following this documentation, we have subscribed to window.addEventListener in order to do so, but it's capturing almost nothing. Then, we started using Angular's $exceptionHandler and it solves the issue partially, but it's still missing the HTTP and script errors. Details:
resource errors (image download errors, script parse errors) are not being handled by window.addEventListener nor by Angular's $exceptionHandler
errors thrown by controller loading, ionic events like beforeEnter, ionic methods called from the UI through template events bindings, ajax calls... none of them are handled by window.addEventListener - Angular's $exceptionHandler does handle them
Surprisingly, all code scheduled with a setTimeout from all those places is properly handled by window.addEventListener
Next files demonstrates all what has been assured:
// app.js
angular.module("starter", [dependencies]).config(function () {
window.addEventListener("error", function (event) {
console.error("Error caught", event);
});
});
.
// controllers.js
angular.module('starter.controllers', [dependencies]).controller("HomeCtrl", function ($scope, $http) {
var nullVariable = null;
var assignmentTarget;
$scope.testClick = function () {
setTimeout(function () {
assignmentTarget = nullVariable.testClickTimeout; // handled
}, 0);
assignmentTarget = nullVariable.testClick; // unhandled
};
$scope.$on('$ionicView.beforeEnter', function () {
setTimeout(function () {
assignmentTarget = nullVariable.homeCtrlBeforeEnterTimeout; // handled
}, 0);
assignmentTarget = nullVariable.homeCtrlBeforeEnter; // unhandled
});
$http({
method: "GET",
responseType: "json",
url: "comeurl"
}).then(function (response) {
setTimeout(function () {
assignmentTarget = nullVariable.ajaxResponseTimeout; // handled
}, 0);
assignmentTarget = nullVariable.ajaxResponse; // unhandled
});
setTimeout(function () {
assignmentTarget = nullVariable.homeCtrlTimeout; // handled
}, 0);
assignmentTarget = nullVariable.homeCtrl; // unhandled
});
Related
I have been trying to load several CSV files before running the code on my page as it uses the data from the CSV files. I have used PAPAPARSE.js as a library to help me with this and I have come up with the following solution.
function loadData(){
console.log("Loading Data!")
loadNodeData();
loadEdgeData();
loadHeadendData();
setup();
}
function loadNodeData(){
Papa.parse("Data/CSV1.csv", {
download: true,
step: function(row) {
NodeData.push(row.data)
},
complete: function() {
console.log("Loaded Node Data!");
load1 = true;
}
});
}
function loadEdgeData(){
Papa.parse("Data/CSV2.csv", {
download: true,
step: function(row) {
EdgeData.push(row.data)
},
complete: function() {
console.log("Loaded Edge Data!");
load2 = true;
}
});
}
function loadHeadendData(){
Papa.parse("Data/CSV3.csv", {
download: true,
step: function(row) {
HeadendArr.push(row.data)
},
complete: function() {
console.log("Loaded Headend Data!");
load3=true;
}
});
}
function setup() {
intervalID = setInterval(isDataLoaded,100)
}
function isDataLoaded(){
//Attempt to setup the page, this will only work if the data iss loaded.
if(load1 && load2 && load3){
console.log("LOADED");
_setupSearchOptions();
}
}
I have this following setup, however i don't know if this is the best way to go about doing something like this. the loadData triggers on page load
<head onload="loadData()">
Is this the correct way to make the program flow?
A more modern approach is to use promises.
You can cut down the code repetition by creating one function that passes in the url and step array to push to and wrap the Papa.parse() call in a promise that gets resolved in the complete callback.
Then use Promise.all() to call _setupSearchOptions() after all three promises resolve
Something like:
function parseCsv(url, stepArr){
return new Promise(function(resolve, reject){
Papa.parse(url, {
download:true,
step: function(row){
stepArr.push(row.data)
},
complete: resolve
});
});
}
function loadData(){
const nodeReq = parseCsv("Data/CSV1.csv", NodeData);
const edgeReq = parseCsv("Data/CSV2.csv", EdgeData);
const headReq = parseCsv("Data/CSV3.csv", HeadendArr);
Promise.all([ nodeReq, edgeReq, headReq]).then(_setupSearchOptions);
}
Note that no error handling has been considered here. Presumably the Papa.parse api also has some fail or error callback that you would use to call the reject() and use a catch() with Promise.all() to handle that failure
Trying to replicate an example I have encountered the problem that the connection is not made, when it comes to do it from a server to my computer, but when I work remotely if it works.
Links example
link 1
link 2
This is my code
var app = angular.module('app', []);
app.value('$', $);
app.factory('signalRSvc', function ($, $rootScope) {
return {
proxy: null,
initialize: function (acceptGreetCallback) {
//Getting the connection object
connection = $.hubConnection('http://190.109.185.138:8016');
//Creating proxy
this.proxy = connection.createHubProxy('HelloWorldHub');
//Starting connection
connection.start({ jsonp: true }).done(function () {
alert("funciono");
});
connection.start({ jsonp: true }).fail(function () {
alert("fallo");
});
//connection.start({ jsonp: true }).done(function () {
// console.log("connection started!");
//});
//Attaching a callback to handle acceptGreet client call
this.proxy.on('acceptGreet', function (message) {
$rootScope.$apply(function () {
acceptGreetCallback(message);
});
});
},
sendRequest: function (callback) {
//Invoking greetAll method defined in hub
this.proxy.invoke('greetAll');
}
}
});
app.controller('SignalRAngularCtrl', function ($scope, signalRSvc) {
$scope.text = "";
$scope.greetAll = function () {
signalRSvc.sendRequest();
}
updateGreetingMessage = function (text) {
$scope.text = text;
}
signalRSvc.initialize(updateGreetingMessage);
});
You should only have one connection.start() and not two. You need to add the done() and fail() into that call.
connection.start({ ... }).done(function() {...}).fail(function() {...})
Otherwise you'll try to start it twice. It might seem to work locally since there is no delay but in actual conditions the first won't finish before the second.
I have an app with AngularJS.
This app makes use of many directives with and without isolated scope. There are two services that loads and dispatch data using a sub/pub system. No big deal.
Now I have the follow scenario: everytime any service start a "get/post/etc" method, I want to show a preloader. When the data comes back, I want hide the preloader. Well, ok, the services dispatch an event, the Preloader directive listen for that and, on success/error callback, a new event is dispatched and I hide the preloader.
But in real world it is not useful. I need to dispatch events like "onStartLoad" from my services and it is polluting the code. Here is an example of one method inside my Services:
var service = {
onOffersLoaded: new signals.Signal(),
// (... many other events/signals here)
// On start any request event:
onStartAny: new signals.Signal(),
// On end any request event:
onEndAny: new signals.Signal(),
getOffers: function(store) {
// Dispatch that the request is about to begin:
service.onStartAny.dispatch();
var url = config.apiUrl + "/my-ending-point-here";
$http.get(url)
.success(function(data) {
// Dispatch that the request got back:
service.onEndAny.dispatch();
service.onOffersLoaded.dispatch(data, store);
})
.error(function(error) {
// Dispatch that the request got back:
service.onEndAny.dispatch();
service.onError.dispatch(error);
});
},
As you can see, I need to spread service.onStartAny.dispatch(); and service.onEndAny.dispatch(); all around my methods. It is very annoying and dirty.
Then I thought: I could use a Interceptor. E very time data comes in or out of my application, an Interceptor could 'catch' those requests and it could dispatch an event to my Preloader directive. Doing this, my Services would not have to deal with those "starting/ending requests" events.
But I do not know how to "access" the directive from my Interceptor OR how to add callbacks to my interceptor from the Directive. Is it possible? Or the only way would be "rootScope broadcast" from Interceptor?
Any help is very appreciate.
Thank you.
The answer I found was simple: since Interceptors are just angular's factory, it is injected in my controller just like any other service.
So, in the end, this is my interceptor:
angular.module("offersApp").factory("preloaderInterceptor", function($q) {
"ngInject";
var interceptor = {
onRequestStart: new signals.Signal(),
onRequestEnd: new signals.Signal(),
request: function(config) {
interceptor.onRequestStart.dispatch();
return config;
},
requestError: function(rejection) {
interceptor.onRequestEnd.dispatch();
return $q.reject(rejection);
},
response: function(response) {
interceptor.onRequestEnd.dispatch();
return response;
},
responseError: function(rejection) {
interceptor.onRequestEnd.dispatch();
return $q.reject(rejection);
}
};
return interceptor;
});
And this is my preloader directive:
angular.module("offersApp").directive("preloader", function ($timeout, preloaderInterceptor) {
"ngInject";
return {
template: '<div id="preloader"><div class="loading"></div></div>',
replace: true,
restrict: "EA",
scope: {},
link: function (scope, element, attrs, ctrl) {
var showPreloader = function() {
element.css({display: 'block', opacity: 1});
}
var hidePreloader = function() {
element.css({opacity: 0});
var promise = $timeout(function() {
element.css({display: 'none'});
}, 600);
}
preloaderInterceptor.onRequestStart.add(showPreloader);
preloaderInterceptor.onRequestEnd.add(hidePreloader);
}
};
});
function getDBCounts() {
$http.get('URL')
.then(function (response){
$scope.stats = response.data;
$scope.getDBCountsTimeOut = setTimeout(getDBCounts, 5000);
}, function () {
$scope.getDBCountsTimeOut = setTimeout(getDBCounts, 5000);
})
}
$scope.$on("$destroy", function () {
clearTimeout($scope.getDBCountsTimeOut);
});
when ever I am going to different controller I am making sure I am cancelling all timeouts but the issue is when the $http call is in pending and I am moving to different controller at the same time (the timeout is not being created until we get the response) and I am unable to cancel that timeout on controller change since the call is in pending state and I cannot clear a timeout which is not been created.
How do I handle this situation. what is the best solution for this issue.
I have done this but in the error section I am unable to differentiate the network error and cancelled timeout because I need to still call setTimeout if it is network error.
$scope.canceler = $q.defer();
function getDBCounts() {
$http
.get(apiUri + '/backend/database/stats', {timeout: $scope.canceler.promise})
.then(function (response){
$scope.stats = response.data;
$scope.getDBCountsTimeOut = setTimeout(getDBCounts, 5000);
}, function (er, second) {
$scope.getDBCountsTimeOut = setTimeout(getDBCounts, 5000);
})
}
$scope.$on("$destroy", function () {
clearTimeout($scope.getDBCountsTimeOut);
$scope.canceler.resolve();
});
For both network error and timeout i get this as a response:
{config: Object
data: null
headers: (name)
status: 0
statusText: ""}
Now how do I resolve this issue.
Thanks in advance.
This seems to be a limitation of the $http API. I would also have expected that you can tell from the response/error, whether the request has timed out.
The only way I see here is that you don't cancel the promise but set a flag in the scope when it has been destroyed. Then you simply don't set the timeouts when the scope has already been destroyed:
function getDBCounts() {
$http.get('URL')
.then(function (response){
$scope.stats = response.data;
})
.finally(function() {
if (!$scope.destroyed) {
$scope.getDBCountsTimeOut = setTimeout(getDBCounts, 5000);
}
})
}
$scope.$on("$destroy", function () {
clearTimeout($scope.getDBCountsTimeOut);
$scope.destroyed = true;
});
Your approach is better and would be the "correct" way. However, it seems this is not possible.
PS: If you want to do something regardless of whether a promise has been resolved or rejected, you should use finally (see above). Please note that in older browser (and thus older JS/ECMA-Script versions) finally is a reserved word. If you want to make sure that older browsers are supported, call it like this:
$http.get(...)["finally"](function() {
})
I am trying to implement a service inside of my WinJS Windows 8 App, the service needs to call winjs httpClient. I want my service to return a promise while it waits for the promise returned by httpClient . My service code is as follows
(function () {
"use strict";
var _httpClient = new Windows.Web.Http.HttpClient();
var _infoUri = new Windows.Foundation.Uri("https://news.google.com/");
var _getLatestInfo = function () {
return WinJS.Promise(function (completeCallback, errorCallback, progressCallback) {
// invoke the httpclient here
_httpClient.getAsync(_infoUri)
.then(function complete(result) {
completeCallback(result);
}, function error(result) {
errorCallback(result);
}, function progress(result) {
progressCallback(result);
});
});
};
WinJS.Namespace.define("DataService", {
getLatestInfo: _getLatestInfo
});
})();
And I call my service method as follows
(function () {
"use strict";
WinJS.UI.Pages.define("/pages/home/home.html", {
// This function is called whenever a user navigates to this page. It
// populates the page elements with the app's data.
ready: function (element, options) {
// TODO: Initialize the page here.
DataService.getLatestInfo().then(function () { }, function () { }, function () { });
}
});
})();
This does not work and I get an error like this
Exception was thrown at line 2018, column 13 in ms-appx://microsoft.winjs.2.0/js/base.js
0x800a01b6 - JavaScript runtime error: Object doesn't support property or method '_setState''
I tried simplifying my service as follows with no luck.
(function () {
"use strict";
var _getLatestInfo = function () {
return WinJS.Promise(function (a, b, c) { });
};
WinJS.Namespace.define("DataService", {
getLatestInfo: _getLatestInfo
});
})();
I don't know what the error is trying to tell me and how to correct this.
I just figured it out, I was missing the 'new' just before WinJS.Promise. Since WinJS.Promise is a class it apparently need to be newed. The error message only confused me more.