-
Notifications
You must be signed in to change notification settings - Fork 27.5k
$q: Control flow duplication (forking) when uncaught error is handled in .then() #14745
Comments
This is so by design. Passing it to the There has been a debate (of medium length) about whether we should pass the thrown error to the I think the verdict was that making it configurable won't harm (but we haven't decided on the default setting yet 😛 ) If anyone wants to submit a PR, that would be awesome (should be an easy change - famous last words). |
Thanks for the response! The control flow is "forked" in a sense that if there is e.g. an "email to developer" in the catch-all handler, and also in the I'll see about the PR, I'm super-busy lately.. (aren't we all?) |
This sounds like a breaking change, and one goes against the Promise A+ spec - see relevant line here. I would recommend against this. |
@wesleycho do you mean "If either onFulfilled or onRejected throws an exception e, promise2 must be rejected with e as the reason." ? Yes that is the correct behavior and it already happens. However the problem is that it is not the only thing that happens in that case, but the error is also (and firstly) reported to Angular's uncaught exception handler - which is incorrect because the definition of an uncaught exception is, well, one that is uncaught. However in this particular case the exception is caught by the Promise.then() and then, passed to .catch(), and only if there are no callbacks registered in any of the .catch() upwards in the Promise call chain, only then is the Exception actually uncaught and should be passed to the Angular's |
Ah, so you're proposing to just have the exceptionHandler call be removed and let it defer down to the unprocessed exception check then. That sounds reasonable, probably wouldn't be difficult to put together a PR doing so. |
@youurayy as stated #14745 (comment) this was discussed. I cannot speak in behalf of the core team, but I think that the transformation of catching an exception and generating a rejected promise is something that needs to be tracked (and if there is a need for this not to be tracked, then the promise should return a rejected promise). |
To be clear, what we are talking about here is just this line and that line; whether or not What I was proposing above, is to make it configurable - similar to how the user can turn on/off the unhandled rejection error. The user should be able to decide whether the |
@gkalpak - the behavior where the |
Not sure what you mean. Isn't that already implemented? |
No, the behavior I'm observing is that the |
@youurayy, there are several
The second category is not relevant with what we are discussing here. This issue is about the first category of calls. Regardless of what we do with the first category though, the unhandled rejections will still be treated as they are today (i.e. calling |
Or maybe I am totally missing your point (it's not that it hasn't hapened before 😃). An example might help: // Assuming:
function onSuccess1(val) { console.log('onSuccess1'); return val; }
function onSuccess2(val) { console.log('onSuccess2'); return val; }
function onError1(err) { console.log('onError1'); return $q.reject(err); }
function onError2(err) { console.log('onError2'); return $q.reject(err); }
function onError3(err) { console.log('onError3'); }
p1 = somePromise1.then(function () { throw 'Error'; }).then(onSuccess1, onError1);
p2 = somePromise2.then(function () { throw 'Error'; }).then(onSuccess2, onError2).catch(onError3); For I suggest that we make it configurable whether the first call to |
We should treat errors thrown in promise callbacks and returned rejections in the same way, all promise libraries do it like that. Let's do it in 1.6.0. |
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. BREAKING CHANGE: Previously, throwing an error from a promise's `onFulfilled` or `onRejection` handlers, would result in passing the error to the `$exceptionHandler()` (in addition to rejecting the promise with the error as reason). Now, a thrown error is treated exactly the same as a regular rejection. This 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. Fixes angular#3174 Fixes angular#14745
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. Fixes angular#3174 Fixes angular#14745 Closes angular#15213 BREAKING CHANGE: Previously, throwing an error from a promise's `onFulfilled` or `onRejection` handlers, would result in passing the error to the `$exceptionHandler()` (in addition to rejecting the promise with the error as reason). Now, a thrown error is treated exactly the same as a regular rejection. This 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.
Triggered when:
Correctly handled when:
Relevant code:
angular.js/src/ng/q.js
Line 366 in c9dffde
deferred.reject(e);
exceptionHandler(e);
, which effectively forks the control flow, if the Promise chain in 1. was not ignored.Normally this is not a ciritical issue, since
exceptionHandler()
is usually used to just print errors to the console, however correctly the error would make it to theexceptionHandler()
only if it was not ignored by thedeferred.reject(e);
chain (which, I'm not sure if it's even feasible to implement).Possibly related; #7992, #13653
The text was updated successfully, but these errors were encountered: