-
Notifications
You must be signed in to change notification settings - Fork 27.5k
Chained promises broken when testing due to $exceptionHandler interference #3174
Comments
I'm wondering if this has been looked at in the meantime? I'm having the same problem where my code is correctly throwing an error to reject a promise and then handle it in my .catch() handler, but angular is intercepting the error and immediately propagating it upward making karma think there was an uncaught error. |
+1 I also find this behaviour odd. I guess a workaround is to configure the exceptionhandler to log instead of rethrow exception. I use this in the relevant tests:
|
$q is not promises/A compliant, IIRC. But it's definitely something to keep in mind for a future rewrite @caitp |
It is actually aplus compliant, at least a few months ago it was, not sure how much aplus has changed since then =) Work is being done on refactoring the logging of exceptions (ITF: rejections in general), however @rasmusvhansen's solution is really the best you can do right now, short of monkey-patching ngMock |
Hey @alexeagle, how are you doing? You still like testing right? That means you should fix this bug ;) Here's a jsfiddle demonstrating it: http://jsfiddle.net/tz8f7tvs/2/ |
Hi Nate, I do like testing. But I don't know anything about Angular 1. Do you want to spend an "escalation token" on this one? :) |
Oh, I didn't realize the teams were split, too! I saw Angular 2 just launched publicly, so congratulations on that. As a Xoogler, I'm not sure I get escalation tokens 😳 If I do, though, I'd love to see this fixed, since I think it makes it really hard to write good tests in some cases. |
Escalation token noted but placed on side board, with reason: "Travel and Conferences." I can take a look at this one in the "next month" kinda timeframe. |
Cool, thanks for taking a look! |
This is not a bug, it is a feature 😛 The exception is indeed turned into a rejection, but at the same time passed to the exceptionHandler. So, @rasmusvhansen's solution is the way to go. The reason why thrown exceptions are passed to the exceptionHandler (in addition to being converted to rejections), was that they might indicate a programming error, which would otherwise get possibly swallowed in an unhandled rejection. Since this turned out to be inconvenient/confusing for people, we are planning to remove this "feature" in 1.6.0. |
Oh, okay! Thanks for getting back to me. I think this behavior is pretty crazy, but then you're already going to remove it, so no need for me to convince you. Please document this behavior in the Is there a doc somewhere saying that And again, thanks for getting back :) |
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
@n-rook, hey, we run |
Alright, thanks @gkalpak! To be honest, I just wanted a canonical source to resolve my stackoverflow question ;) You could try to get Angular 1 listed on this page, but I have no clue how that page was set up or if anyone but me would ever have found it. I think I recognize your username... if you see the TAP team anytime soon, say hi to them for me. |
No idea what the TAP team is, but if I happen to see them I will say hi 😃 |
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.
The Promises specification has the following to say about chaining promises:
However, when using Karma to test a module, I noticed that the following inside the method under test failed:
When invoking the method as follows, with a mocked HTTP backend to force a failure:
Instead of the expectation being met in the
onRejected
function, the exception propagates up the stack and fails the test. This is because apparently during tests, the default policy of$exceptionHandler
is rethrow.I don't understand what business $exceptionHandler has at all being invoked by $q, especially since this behavior violates the Promises/A specification. Exceptions being thrown is a normal and expected part of promise execution.
The text was updated successfully, but these errors were encountered: