-
Notifications
You must be signed in to change notification settings - Fork 27.5k
$q reports errors in promise callbacks even when they are caught #7992
Comments
This doesn't seem unreasonable, I can put together a patch for this tomorrow |
Agreed that the systematic logging of exceptions thrown from callbacks is a bit "noisy". This was already discussed here: #7187 (comment) |
So, I don't think I fully understand Bluebird's way of doing this --- they manage to determine if a promise is handled synchronously and asynchronously (example http://jsfiddle.net/8ga3U/). I think this is fixable by adding some extra state to $q Promise objects, so that tracking whether a rejection is handled or not can happen when then() is called, rather than during resolution (which my current prototype does) My current prototype "solves" this by adding an extra task queue to be consumed after the nextTick() queue, so that handled rejections can turn off their 'logging'. Only thrown exceptions behave this way, so normal rejections are just processed regularly (talking about my code here, although I believe the code in the tree behaves the same way). Thrown errors are wrapped, and error handlers can mark errors as handled, so that a scheduled error logger doesn't need to do anything. This works, but obviously only hides messages when rejections are handled in the same turn that they're resolved in, which is basically never So there's a current implementation (by which I mean, my work-in-progress, not code in the tree) which doesn't work very well in a lot of cases, and a proposed solution (which I think should be good enough, but I'm not sure without trying it). There were plans a while ago to change the Promise implementation to use prototype chains to avoid creating so much garbage, but not much progress has been made there yet. However, finishing that would make it a bit easier to add extra state to the promise objects in a manageable way, I think. It's a bit sad that it's turning out to be harder than expected to improve the error logging in $q, but I can keep hacking on it. |
This is a bit frustrating; they should be symmetric. Tagging in @petkaantonov to see if he can help explain Bluebird's technique. |
I think I sorted out my understanding of that particular part of it while writing my little summary there -- I guess Bluebird must be deciding of a rejection is handled at the time handlers are registered (eg then), and not during resolution. That would make sense. So angular doesn't really have a single then-able, but more like 3 different ones, and it seems not very fun to try and make them all behave the same way in this case, which is why I think unifying those into a single class might make sense first |
The simple version goes like this:
Adding a rejection handler to a promise sets the flag off if it's on. (In cases like promise.all which is optimized to not even attach any handlers on a promise, this is simulated). In function
Then the default handler for the event is to log stack trace to stderr or The actual implementation used in bluebird is a bit different because it provides another event for |
The issue report says: "This isn't a spec compliance issue (since the error isn't thrown, just logged), but it's a developer ergonomics issue." But this is incorrect. The error is not just logged. The error is sent to whatever exceptionHandler service is configured, which by default logs. But in some apps it might pop up a modal for the user, or send a message to the server or log to localStorage. |
The exceptionHandler service (with the exception of the mock exceptionHandler service) isn't really "configurable". You could decorate the exceptionHandler with a replacement to show a modal dialog, but in practice nobody really does this (*read: very few people do this, if any). Since there are so many things which cause $exceptionHandler to be invoked, and there are so many things you probably don't want to display a modal dialog for, and you don't have the context of whatever caused the exception to be thrown, the chances of doing anything interesting with it are next to nil. We can effectively treat $exceptionHandler as a logging service |
Even if we agreed that it is only a logging service, it's a fact that some people are logging to the server side: http://www.davecap.com/post/46522098029/using-sentry-raven-js-with-angularjs-to-catch So the amount of useless stuff in this log could get crazy. But with respect to popping up a modal: my intent in an app was to look at the exception name and pop up a modal based on exception type. That's not much different than having a "top level exception handler" which reports some exceptions more aggressively than others. |
Now I can silently rethrow error using $http.get().catch(function(err) {
if(!canBeHandled) {
// propagate error to further promises
return $q.reject(err);
}
}); But that means that all my services/controllers which use $http.get().catch(function(err) {
if(!canBeHandled) {
// causes call to $exceptionHandler
throw new Error(err);
}
}); Now it calls |
@just-boris I totally agree 👍 I run into this problem while simplifying services based on I spent some time to figure out that Thankfully the project is quite fresh so refactoring is not a major issue. |
What is thought to be the desired behavior is that it only report rejected promises' errors when there is no rejection handler registered on the promise(that is, never called then(*,handler) on it), which is like Q or Bluebird or ES6 primitive Promise or Node.js |
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 Tests that depend on specific order or number of messages in $exceptionHandler will need to handle rejected promises report. Closes: angular#13653 Closes: angular#7992
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 Tests that depend on specific order or number of messages in $exceptionHandler will need to handle rejected promises report. Closes: angular#13653 Closes: angular#7992
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 Tests that depend on specific order or number of messages in $exceptionHandler will need to handle rejected promises report. Closes: angular#13653 Closes: angular#7992
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 Tests that depend on specific order or number of messages in $exceptionHandler will need to handle rejected promises report. Closes: angular#13653 Closes: angular#7992
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. Closes: angular#13653 Closes: angular#7992
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. Closes: angular#13653 Closes: angular#7992
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. Closes: angular#13653 Closes: angular#7992
Angular 1.5 has a bug that causes angular's `$exceptionHandler` to be called for rejected `$q` promises even if they have a rejection handler. This bug caused duplicate error messages in Etcher. A consequence of upgrading to Angular 1.6 is that `$q` promises without a rejection handler will throw `Possibly unhandled rejection` errors. To avoid these errors, this commit moves code responsible for opening a tooltip from the template to the controller and handles the rejection. Other packages upgraded: - angular-moment to v1.0.1 - angular-ui-router to v0.4.2 - angular-mocks to v1.6.1 Change-type: patch Changelog-Entry: Fix duplicate error messages Fixes: #1082 See: angular/angular.js#7992 See: angular/angular.js#13662
Angular 1.5 has a bug that causes angular's `$exceptionHandler` to be called for rejected `$q` promises even if they have a rejection handler. This bug caused duplicate error messages in Etcher. A consequence of upgrading to Angular 1.6 is that `$q` promises without a rejection handler will throw `Possibly unhandled rejection` errors. To avoid these errors, this commit moves code responsible for opening a tooltip from the template to the controller and handles the rejection. Other packages upgraded: - angular-moment to v1.0.1 - angular-ui-router to v0.4.2 - angular-mocks to v1.6.1 Change-type: patch Changelog-Entry: Fix duplicate error messages Fixes: #1082 See: angular/angular.js#7992 See: angular/angular.js#13662
Angular 1.5 has a bug that causes angular's `$exceptionHandler` to be called for rejected `$q` promises even if they have a rejection handler. This bug caused duplicate error messages in Etcher. A consequence of upgrading to Angular 1.6 is that `$q` promises without a rejection handler will throw `Possibly unhandled rejection` errors. To avoid these errors, this commit moves code responsible for opening a tooltip from the template to the controller and handles the rejection. Other packages upgraded: - angular-moment to v1.0.1 - angular-ui-router to v0.4.2 - angular-mocks to v1.6.1 Change-type: patch Changelog-Entry: Fix duplicate error messages Fixes: #1082 See: angular/angular.js#7992 See: angular/angular.js#13662
Plunker: http://plnkr.co/edit/5vMj4AJibWcnTKYR5z73?p=preview
I have caught the error and used it to display useful information to my user in the UI. However, Angular still logs a big ol' stack trace to the console.
This isn't a spec compliance issue (since the error isn't thrown, just logged), but it's a developer ergonomics issue. I don't understand how using $q on any reasonably large promise project would not just result in a console filled with spurious errors.
As always, the correct time to report a rejection as unhandled is debatable. Bluebird does it at the end of turn, whereas Q doesn't do it at all, relying on
.done()
instead. But this isn't even about unhandled rejections---it's about all rejections!The Bluebird strategy seems like a pure win: it would remove this spurious reporting, but keep it for any cases that could potentially be real.
The text was updated successfully, but these errors were encountered: