I did find a way to handle uncaught exceptions in angularjs by using predefined $exceptionHandler service. According to the doc, I'm delegating exceptions manually by using try {...} - catch(e). See below
try {
$scope.loadImage();
}catch (e){
console.log(e);
}
It is working absolutely fine
But I eager to know how could I handle uncaught exceptions in angular way ? Can anyone please share a working example ?
It is explained in the API.
angular.module('exceptionOverride', []).factory('$exceptionHandler', function() {
return function(exception, cause) {
exception.message += ' (caused by "' + cause + '")';
throw exception;
};
});
This example will override the normal action of
$exceptionHandler, to make angular exceptions fail hard when they
happen, instead of just logging to the console.
Note, that code executed in event-listeners (even those registered
using jqLite's on/bind methods) does not delegate exceptions to the
$exceptionHandler (unless executed during a digest). If you wish,
you can manually delegate exceptions, e.g. try { ... } catch(e) {
$exceptionHandler(e); }
Related
I'm using AngularJS version of 1.7.2 and got an exception with this message
Cannot read property 'dataItem' of undefined
and it is not throwing into console / customExceptionHandling Service Because code in AngularJS is below:
catch (e) {
rejectPromise(promise, e);
// This error is explicitly marked for being passed to the $exceptionHandler
if (e && e.$$passToExceptionHandler === true) {
exceptionHandler(e);
}
}
that $$passToExceptionHandler is not present in e object and exceptionHandler function is not calling.
Can anyone please explain to me why this is happening?
You might have disabled reporting unhandled rejected promises with this line:
qProvider.errorOnUnhandledRejections(false);
But generally more information about the error source are required to give you better answer.
Maybe this will work, set $$passToExceptionHandler to all "Error" instance like:
Error.prototype.$$passToExceptionHandler = true
I have recently updated my application from Angular 1.5 to 1.6.3 and started getting Jasmine unit test failures (with PhantomJS) around promise based code I have written:
Possibly unhandled rejection: undefined thrown
Reading around I see that the accepted solution is to chain .then() with .catch() blocks to handle the rejections gracefully.
I have done this for one of my source files that I am testing to prove this gets past the error which it does.
However, it has now uncovered a further issue where an expectation I am testing when a promise rejection is called in my code is no longer passing.
This is the function I am trying to test (after adding the required catch blocks)
public deleteSomething = (thing) => {
return this.UserMessages.buildConfirmDialog().then(() => {
this.someService.remove(thing)
.then(() => {
this.UserMessages.showToast('Something deleted');
})
.catch((error) => {
//handle error
});
})
.catch((error) => {
//handle error
});
}
And here is the test:
var thing = {foo: 'bar'},
deferredRemove,
deferredConfirm,
//Mock service below injected into controller later on before test are run
UserMessages = {
buildConfirmDialog: jasmine.createSpy('buildConfirmDialog').and.callFake(function() {
deferredConfirm = $q.defer();
return deferredConfirm.promise.catch(angular.noop);
})
};
//Inject and controller setup here...
describe('When deleting something', function() {
beforeEach(function() {
deferredRemove = $q.defer();
spyOn(someService, 'remove').and.returnValue(deferredRemove.promise.catch(angular.noop));
});
describe('and the user confirms the deletion', function() {
beforeEach(function() {
ctrl.deleteSomething(thing);
deferredConfirm.resolve();
deferredRemove.resolve();
$rootScope.$apply();
});
it('should call remove on someService', function() {
console.log('someService.remove.calls = ' + someService.remove.calls.count());
expect(someService.remove).toHaveBeenCalled();
});
});
describe('and the user cancels the deletion', function() {
beforeEach(function() {
someService.remove.calls.reset();
vm.deleteSomething(thing);
deferredConfirm.reject({});
$rootScope.$apply();
});
it('should not call remove on someService', function() {
console.log('someService.remove.calls = ' + someService.remove.calls.count());
expect(someService.remove.calls.count()).toEqual(0);
});
});
});
I didnt have the .catch(angular.noop) parts in prior to upgrading to 1.6.3 and came across some posts suggesting to do this in order to make the tests happy, which certainly helped for me in getting past the unhandled rejection error in my test run.
The problem I am now facing is that for the reject test spec, there should be no call made to a remove function in my service, and so the number of calls should be zero, but it keeps coming out as 1. I added the line to reset the calls in my test to be sure it wasnt the previous test contributing (I know calls are meant to be reset between tests).
This test was running just fine when I was on 1.5, so this has to be something with the way my code\test is written not playing nicely with changes in 1.6.x
Can someone shed some light on what may be going on here please?
Thanks
I didnt have the .catch(angular.noop) parts in prior to upgrading to 1.6.3 and came across some posts suggesting to do this in order to make the tests happy, which certainly helped for me in getting past the unhandled rejection error in my test run.
Adding .catch(angular.noop) will certainly handle the unhandled rejection.
It converts the rejected promise to a fulfilled promise!!
Your test is correctly failing because you broke your code.
For more information, see Catch method not working with $http get request
Changes to $q for AngularJS V1.6
report promises with non rejection callback
Rejected promises that do not have a callback to handle the rejection report
this to $exceptionHandler so they can be logged to the console.
BREAKING CHANGE
Unhandled rejected promises will be logged to $exceptionHandler.
Tests that depend on specific order or number of messages in $exceptionHandler
will need to handle rejected promises report.
treat thrown errors as regular rejections
Previously, errors thrown in a promise's onFulfilled or onRejected handlers were treated in a
slightly different manner than regular rejections:
They were passed to the $exceptionHandler() (in addition to being converted to rejections).
The reasoning for this behavior was that an uncaught error is different than a regular rejection, as
it can be caused by a programming error, for example. In practice, this turned out to be confusing
or undesirable for users, since neither native promises nor any other popular promise library
distinguishes thrown errors from regular rejections.
(Note: While this behavior does not go against the Promises/A+ spec, it is not prescribed either.)
This commit removes the distinction, by skipping the call to $exceptionHandler(), thus treating
thrown errors as regular rejections.
Note:
Unless explicitly turned off, possibly unhandled rejections will still be caught and passed to the
$exceptionHandler(), so errors thrown due to programming errors and not otherwise handled (with a
subsequent onRejected handler) will not go unnoticed.
For more information, see AngularJS Developer Guide - Migrating from V1.5 to V1.6
disable Possibly Unhandled Rejection by this config and test again.
app.config(['$qProvider', function ($qProvider) {
$qProvider.errorOnUnhandledRejections(false);
}]);
In the following code, an exception is caught by the catch function of the $q promise:
// Fiddle - http://jsfiddle.net/EFpn8/6/
f1().then(function(data) {
console.log("success 1: "+data)
return f2();
})
.then(function(data) {console.log("success 2: "+data)})
.catch(function(data) {console.log("error: "+data)});
function f1() {
var deferred = $q.defer();
// An exception thrown here is not caught in catch
// throw "err";
deferred.resolve("done f1");
return deferred.promise;
}
function f2() {
var deferred = $q.defer();
// An exception thrown here is handled properly
throw "err";
deferred.resolve("done f2");
return deferred.promise;
}
However when I look in the console log output I see the following:
The exception was caught in Angular, but was also caught by the error handling of the browser. This behavior does reproduce with Q library.
Is it a bug? How can I truly catch an exception with $q?
Angular's $q uses a convention where thrown errors are logged regardless of being caught. Instead, if you want to signal a rejection you need to return $q.reject(... as such:
function f2() {
var deferred = $q.defer();
// An exception thrown here is handled properly
return $q.reject(new Error("err"));//throw "err";
deferred.resolve("done f2");
return deferred.promise;
}
This is to distinguish rejections from errors like SyntaxError. Personally, it's a design choice I disagree with but it's understandable since $q is tiny so you can't really build in a reliable unhandled rejection detection mechanism. In stronger libraries like Bluebird, this sort of thing is not required.
As a side note - never, ever throw strings : you miss on stack traces that way.
Is it a bug?
No. Looking in the source for $q reveals that a deliberate try / catch block is created to respond to exceptions thrown in the callback by
Rejecting the promise, as through you had called deferred.reject
Calling the registered Angular exception hander. As can be seen in the $exceptionHandler docs, the default behaviour of this is to log it to the browser console as an error, which is what you have observed.
... was also caught by the error handling of the browser
To clarify, the exception isn't handled directly by the browser, but appears as an error because Angular has called console.error
How can I truly catch an exception with $q?
The callbacks are executed some time later, when the current call stack has cleared, so you won't be able to wrap the outer function in try / catch block. However, you have 2 options:
Put in try/catch block around the code that might throw the exception, within the callback:
f1().then(function(data) {
try {
return f2();
} catch(e) {
// Might want convert exception to rejected promise
return $q.reject(e);
}
})
Change how Angular's $exceptionHandler service behaves, like at How to override $exceptionHandler implementation . You could potentially change it to do absolutely nothing, so there would never be anything in the console's error log, but I don't think I would recommend that.
Fixed with AngularJS version 1.6
The reasoning for this behavior was that an uncaught error is different than a regular rejection, as
it can be caused by a programming error, for example. In practice, this turned out to be confusing
or undesirable for users, since neither native promises nor any other popular promise library
distinguishes thrown errors from regular rejections.
(Note: While this behavior does not go against the Promises/A+ spec, it is not prescribed either.)
$q:
Due to e13eea, an error thrown from a promise's onFulfilled or onRejection handlers is treated exactly the same as a regular rejection. Previously, it would also be passed to the $exceptionHandler() (in addition to rejecting the promise with the error as reason).
The new behavior applies to all services/controllers/filters etc that rely on $q (including built-in services, such as $http and $route). For example, $http's transformRequest/Response functions or a route's redirectTo function as well as functions specified in a route's resolve object, will no longer result in a call to $exceptionHandler() if they throw an error. Other than that, everything will continue to behave in the same way; i.e. the promises will be rejected, route transition will be cancelled, $routeChangeError events will be broadcasted etc.
-- AngularJS Developer Guide - Migrating from V1.5 to V1.6 - $q
The deferred is an outdated and a really terrible way of constructing promises, using the constructor solves this problem and more:
// This function is guaranteed to fulfill the promise contract
// of never throwing a synchronous exception, using deferreds manually
// this is virtually impossible to get right
function f1() {
return new Promise(function(resolve, reject) {
// code
});
}
I don't know if angular promises support the above, if not, you can do this:
function createPromise(fn) {
var d = $q.defer();
try {
fn(d.resolve.bind(d), d.reject.bind(d));
}
catch (e) {
d.reject(e);
}
return d.promise;
}
Usage is same as promise constructor:
function f1() {
return createPromise(function(resolve, reject){
// code
});
}
Here is an sample test that shows the new $q construction function, use of .finally(), rejections, and promise chain propagations:
iit('test',inject(function($q, $timeout){
var finallyCalled = false;
var failValue;
var promise1 = $q.when(true)
.then(function(){
return $q(function(resolve,reject){
// Reject promise1
reject("failed");
});
})
.finally(function(){
// Always called...
finallyCalled = true;
// This will be ignored
return $q.when('passed');
});
var promise2 = $q.when(promise1)
.catch(function(value){
// Catch reject of promise1
failValue = value;
// Continue propagation as resolved
return value+1;
// Or continue propagation as rejected
//return $q.reject(value+2);
});
var updateFailValue = function(val){ failValue = val; };
$q.when(promise2)
.then( updateFailValue )
.catch(updateFailValue );
$timeout.flush();
expect( finallyCalled ).toBe(true);
expect( failValue ).toBe('failed1');
}));
When I loose net connection and try to load a HTML template(which is not loaded previously) using angularJS it gives me error, net::ERR_INTERNET_DISCONNECTED.
Is there any way to call a function before angular fires error net::ERR_INTERNET_DISCONNECTED , so I will be able to notify user they have lost their connection.
I don't want to put watchers on online/offline status since I want user to be able to see previously loaded HTML templates.
From angularjs docs
angular.module('exceptionOverride', []).factory('$exceptionHandler', function() {
return function(exception, cause) {
exception.message += ' (caused by "' + cause + '")';
throw exception;
};
});
Use this to catch every exception, including internet connection problems, it all runs through this method
I've taken over an Angular application from another developer.
Now I've been playing around with it and making my first edits.
Question is: when I bind to non-existing elements (or make any other mistake) I don't see any error, which sounds good but isn't because I want to be notified when I do something wrong.
How can I make Angular show errors?
To began, I recommend not using the minified version of angular, as the unminified version allows more coherent and clear errors to be logged in the console.
Next, I think the best way to handle angular errors is to write a custom wrapper to better handle them. Here is an example of how you could write a wrapper.
The first step would be to write a function that will handle the error in a way that you want. This is how I current handle angular errors. Note: this could be modified in many different ways to make the error handling more customized.
function HandleAngularError(Exception, AppName){
try {
var AppName = (window.parent._.isEmpty(AppName) ? "Angular App Unspecified" : AppName) + " - ";
if (window.parent._.isUndefined(Exception)) {
console.log(strAppName + "error: exception undefined", "AngularJs");
} else {
console.log(strAppName + "error: " + Exception.toString() + " " + JSON.stringify(Exception), "AngularJs");
}
} catch (e) {
alert("Handle Angular Error: " + Exception.toString() + " " + JSON.stringify(Exception));
}
}
The next step is to include the error handling function in the any of the Modules in you project and rely on the $exceptionHandler to then pass angular errors into your custom wrapper like so:
angular.module("someApp",[], function(){
//setup stuff here
}).factory( '$exceptionHandler', function () {
return function (exception) {
HandleAngularError(exception, "someApp");
};
});
By default, AngularJS is forgiving, that is to say it prefers to show nothing rather than throwing an exception if you binded an undefined value.
From Angular doc :
In JavaScript, trying to evaluate undefined
properties generates ReferenceError or TypeError. In Angular,
expression evaluation is forgiving to undefined and null.
One way of displaying an error would be to decorate the $eval function, which is used to evaluate binded expressions.
You could try something like this :
app.run(function($rootScope) {
var origRootScope = $rootScope,
origEval = origProvider.$eval;
//Override rootScope's $eval with our own
origProvider.$eval = function(expression, locals) {
// Test the expression value and add the behavior you like
if(typeof expression === 'undefined') {
// Log some kind of error
console.log('Some kind of error')
}
// Call the original $eval function
var returnValue = origEval.apply(this, arguments);
return returnValue;
}
});
I haven't tried that code but you should be able to add custom logging to $eval this way.