Catched errors in AngularJS's exception handler [duplicate] - angularjs

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');
}));

Related

$q.reject not working and going back to error block in Angular [duplicate]

I had a look at the bluebird promise FAQ, in which it mentions that .then(success, fail) is an antipattern. I don't quite understand its explanation as for the try and catch.
What's wrong with the following?
some_promise_call()
.then(function(res) { logger.log(res) }, function(err) { logger.log(err) })
It seems that the example is suggesting the following to be the correct way.
some_promise_call()
.then(function(res) { logger.log(res) })
.catch(function(err) { logger.log(err) })
What's the difference?
What's the difference?
The .then() call will return a promise that will be rejected in case the callback throws an error. This means, when your success logger fails, the error would be passed to the following .catch() callback, but not to the fail callback that goes alongside success.
Here's a control flow diagram:
To express it in synchronous code:
// some_promise_call().then(logger.log, logger.log)
then: {
try {
var results = some_call();
} catch(e) {
logger.log(e);
break then;
} // else
logger.log(results);
}
The second log (which is like the first argument to .then()) will only be executed in the case that no exception happened. The labelled block and the break statement feel a bit odd, this is actually what python has try-except-else for (recommended reading!).
// some_promise_call().then(logger.log).catch(logger.log)
try {
var results = some_call();
logger.log(results);
} catch(e) {
logger.log(e);
}
The catch logger will also handle exceptions from the success logger call.
So much for the difference.
I don't quite understand its explanation as for the try and catch
The argument is that usually, you want to catch errors in every step of the processing and that you shouldn't use it in chains. The expectation is that you only have one final handler which handles all errors - while, when you use the "antipattern", errors in some of the then-callbacks are not handled.
However, this pattern is actually very useful: When you want to handle errors that happened in exactly this step, and you want to do something entirely different when no error happened - i.e. when the error is unrecoverable. Be aware that this is branching your control flow. Of course, this is sometimes desired.
What's wrong with the following?
some_promise_call()
.then(function(res) { logger.log(res) }, function(err) { logger.log(err) })
That you had to repeat your callback. You rather want
some_promise_call()
.catch(function(e) {
return e; // it's OK, we'll just log it
})
.done(function(res) {
logger.log(res);
});
You also might consider using .finally() for this.
The two aren't quite identical. The difference is that the first example won't catch an exception that's thrown in your success handler. So if your method should only ever return resolved promises, as is often the case, you need a trailing catch handler (or yet another then with an empty success parameter). Sure, it may be that your then handler doesn't do anything that might potentially fail, in which case using one 2-parameter then could be fine.
But I believe the point of the text you linked to is that then is mostly useful versus callbacks in its ability to chain a bunch of asynchronous steps, and when you actually do this, the 2-parameter form of then subtly doesn't behave quite as expected, for the above reason. It's particularly counterintuitive when used mid-chain.
As someone who's done a lot of complex async stuff and bumped into corners like this more than I care to admit, I really recommend avoiding this anti-pattern and going with the separate handler approach.
By looking at advantages and disadvantages of both we can make a calculated guess as to which is appropriate for the situation.
These are the two main approaches to implementing promises. Both have it's pluses and minus
Catch Approach
some_promise_call()
.then(function(res) { logger.log(res) })
.catch(function(err) { logger.log(err) })
Advantages
All errors are handled by one catch block.
Even catches any exception in the then block.
Chaining of multiple success callbacks
Disadvantages
In case of chaining it becomes difficult to show different error messages.
Success/Error Approach
some_promise_call()
.then(function success(res) { logger.log(res) },
function error(err) { logger.log(err) })
Advantages
You get fine grained error control.
You can have common error handling function for various categories of errors like db error, 500 error etc.
Disavantages
You will still need another catch if you wish to handler errors thrown by the success callback
Simple explain:
In ES2018
When the catch method is called with argument onRejected, the
following steps are taken:
Let promise be the this value.
Return ? Invoke(promise, "then", « undefined, onRejected »).
that means:
promise.then(f1).catch(f2)
equals
promise.then(f1).then(undefiend, f2)
Using .then().catch() lets you enable Promise Chaining which is required to fulfil a workflow. You may need to read some information from database then you want to pass it to an async API then you want to manipulate the response. You may want to push the response back into the database. Handling all these workflows with your concept is doable but very hard to manage. The better solution will be then().then().then().then().catch() which receives all errors in just once catch and lets you keep the maintainability of the code.
Using then() and catch() helps chain success and failure handler on the promise.catch() works on promise returned by then(). It handles,
If promise was rejected. See #3 in the picture
If error occurred in success handler of then(), between line numbers 4 to 7 below. See #2.a in the picture
(Failure callback on then() does not handle this.)
If error occurred in failure handler of then(), line number 8 below. See #3.b in the picture.
1. let promiseRef: Promise = this. aTimetakingTask (false);
2. promiseRef
3. .then(
4. (result) => {
5. /* successfully, resolved promise.
6. Work on data here */
7. },
8. (error) => console.log(error)
9. )
10. .catch( (e) => {
11. /* successfully, resolved promise.
12. Work on data here */
13. });
Note: Many times, failure handler might not be defined if catch() is
written already.
EDIT: reject() result in invoking catch() only if the error
handler in then() is not defined. Notice #3 in the picture to
the catch(). It is invoked when handler in line# 8 and 9 are not
defined.
It makes sense because promise returned by then() does not have an error if a callback is taking care of it.
Instead of words, good example. Following code (if first promise resolved):
Promise.resolve()
.then
(
() => { throw new Error('Error occurs'); },
err => console.log('This error is caught:', err)
);
is identical to:
Promise.resolve()
.catch
(
err => console.log('This error is caught:', err)
)
.then
(
() => { throw new Error('Error occurs'); }
)
But with rejected first promise, this is not identical:
Promise.reject()
.then
(
() => { throw new Error('Error occurs'); },
err => console.log('This error is caught:', err)
);
Promise.reject()
.catch
(
err => console.log('This error is caught:', err)
)
.then
(
() => { throw new Error('Error occurs'); }
)

Unable to test a rejected promise after migrating to Angular 1.6.3

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);
}]);

Equivalent of angular's $q.deferred.reject without $q

I imagine this has to be a pretty normal setup:
return $http.get('some/url')
.then(function (result) {
if (result.data.success) {
//Do something useful
} else {
//We've hit some sort of error
}
},
function () {
return "Failed to communicate with the server, or the server encountered an error.";
});
My questions is around what to do about scenarios where the http call technically succeeds, but the data we get back from the server is bad, unusable, or explicitly indicates something is wrong. What I want to do there is push myself onto the failure track of the promise chain. That way anyone consuming this promise well execute their own failure function, if that makes sense.
I know $q gives you the tools you need to do this by creating a deferred object and then letting you call resolve or reject under any circumstance you want, but I wondered if there was a way to do that without using $q.
To change a promise from the resolved to the rejected state, you can do one of two things from a .then() handler:
Return a rejected promise and the reason for that rejected promise will become the reject reason for the parent promise.
Throw an exception and the exception value will become the reject reason for the parent promise. .then() automatically catches exceptions in .then() handler functions and turns them into rejections.
So, here's one way you could do it:
return $http.get('some/url').then(function (result) {
if (result.data.success) {
//Do something useful
} else {
//We've hit some sort of error, make promise become rejected
throw new Error("invalid data received");
}
}).catch(function (err) {
// log error
console.log(err);
// make sure promise stays rejected
throw err;
});
If you use ES6 you can use JavaScript native promises.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

Promise chain success is executed after first one returns error

i have a promise chain, in which the first promise causes a failure, but even then the second promise is getting executed successfully whereas as expected that should have failed...
PLUNKER LINK
what i did was simple:
dummyPromise().then(success, error).then(success, error);
so if the first one executes error, the subsequent error should be executed but what is get is:
1. error
2. success
why is that so??
The other answer by Nikos is correct - but I feel like this could benefit from a synchronous analogy:
try{
var val = dummyPromise();
} catch (e){
val = "SomeValue";
}
secondHandler(val);
You're catching the error and handling it - so it gets to the success handler.
You have to return return $q.reject(); from a promise in order for the next promise in the chain to fail too. See forked plunker: http://plnkr.co/edit/porOG8qVg2GkeddzVHu3?p=preview
The reason is straightforward: Your error handler may take action to correct the error. Therefore it is not reasonable to have the next promise failing by default. By the way, you can return $q.reject() even from a success handler, if you sense an error condition, to have the next promise in the chain failing.
This is how promises work. In your error-function your dealing with the error,if no specified otherwise, it will return a new promise which is resolved. If you want to reject it, you hav to do it by returning $q.reject();
Have a look at the documentation. I find the example from $q.reject(); explains it well.
promiseB = promiseA.then(function(result) {
// success: do something and resolve promiseB
// with the old or a new result
return result;
}, function(reason) {
// error: handle the error if possible and
// resolve promiseB with newPromiseOrValue,
// otherwise forward the rejection to promiseB
if (canHandle(reason)) {
// handle the error and recover
return newPromiseOrValue;
}
return $q.reject(reason);
});

What $q.defer() really does?

I'm learning about Angular JS and on the moment I'm trying to understand about promises and async programming and I have this doubt about $q.defer(). My point is the following: usually when people work with promises they do something like that, considering that $q is already available
function someAsyncFunction() {
var deferred = $q.defer();
/* Do things and if everything goes fine return deferred.resolve(result)
otherwise returns deferred.reject()
*/
return deferred.promise;
}
What is this really doing? When we do var deferred = $q.defer() it imediately switches all the execution of that function to another thread and return the promise being a reference to the results of this operation that is still performing there?
Is this the way we should think about when creating async methods?
With $q u run functions asynchronously.
Deferred objects signals that something, some task is done.
var defer = $q.defer(); // we create deferred object, which will finish later.
defer.promise // we get access to result of the deferred task
.then( // .then() calls success or error callback
function(param) {
alert("i something promised " + param);
return "something";
}); // u can use 1 or more .then calls in row
defer.resolve("call"); //returns promise
Here example:
http://jsfiddle.net/nalyvajko/HB7LU/29048/
Angular's $q service is based on the Javascript library Q. You can read more about it in the Q documentation, or read the code in the github repo. I think this part snipped from the introduction to the documentation explains it best:
If a function cannot return a value or throw an exception without
blocking, it can return a promise instead. A promise is an object that
represents the return value or the thrown exception that the function
may eventually provide. A promise can also be used as a proxy for a
remote object to overcome latency.

Resources