-
-
Notifications
You must be signed in to change notification settings - Fork 35.4k
promise: warn on unhandled rejections #6439
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
I think that we all _agree_ that the current default behavior in node for unhandled rejections is very wrong, people running code that behaved well in browsers suddenly get their exceptions swallowed by default and have to read the process docs and dig up an API. Programmer errors are swallowed by default and are very hard to find. This makes NodeJS a lot less approachable to promise users* which are slowly but surely becoming a majority as web APIs switch to promises and a lot already have. This pull request suggests - We log the exception with a GUID when `unhandledRejection` happens and the text "Warning: Possibly unhandled rejection in ...". It is treated as a _warning_ and not an error - we use the new `process.warning` API that @jasnell has suggested so it is treated like a warning and not an error. - We log a "Possibly unhandled rejection eventually handled" message (also, as a warning) when that error is taken care of. This approach has the following advantages: - Most backwards compatible with the current behavior while solving the issue for 99% of users. - This takes care of the "always right" issue as it's a warning and there might be valid use cases for not handling it. Kind of like the very helpful EventEmitter # of subscribers warning. - Users hooking on warnings have this dealt with like other warnings, they can suppress it in production, deal with it or do anything else. - Users installing custom handlers have nothing broken in their code and everything proceeds as normal. - Easy to override this behavior either at the promise or warning level.
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,8 @@ | |
|
|
||
| const promiseRejectEvent = process._promiseRejectEvent; | ||
| const hasBeenNotifiedProperty = new WeakMap(); | ||
| const promiseToGuidProperty = new WeakMap(); | ||
| var lastPromiseId = 1; | ||
| const pendingUnhandledRejections = []; | ||
|
|
||
| exports.setup = setupPromises; | ||
|
|
@@ -18,16 +20,23 @@ function setupPromises(scheduleMicrotasks) { | |
|
|
||
| function unhandledRejection(promise, reason) { | ||
| hasBeenNotifiedProperty.set(promise, false); | ||
| promiseToGuidProperty.set(promise, lastPromiseId++); | ||
| addPendingUnhandledRejection(promise, reason); | ||
| } | ||
|
|
||
| function rejectionHandled(promise) { | ||
| var hasBeenNotified = hasBeenNotifiedProperty.get(promise); | ||
| if (hasBeenNotified !== undefined) { | ||
| hasBeenNotifiedProperty.delete(promise); | ||
| const uid = promiseToGuidProperty.get(promise); | ||
| promiseToGuidProperty.delete(promise); | ||
| if (hasBeenNotified === true) { | ||
| process.nextTick(function() { | ||
| process.emit('rejectionHandled', promise); | ||
| if(!process.emit('rejectionHandled', promise)) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| process.emitWarning('Possibly Unhandled Promise Rejection Handled ' | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The message
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, it's all confusing terminology, I'm very open to different naming - the PR is a strawman to discuss this sort of thing exactly. Maybe 'Previous reported promise rejection handled'? |
||
| + '(unhandled rejection id: ' + uid +') ', | ||
| 'PromiseRejectionHandledWarning'); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
|
|
@@ -41,9 +50,12 @@ function setupPromises(scheduleMicrotasks) { | |
| var reason = pendingUnhandledRejections.shift(); | ||
| if (hasBeenNotifiedProperty.get(promise) === false) { | ||
| hasBeenNotifiedProperty.set(promise, true); | ||
| const uid = promiseToGuidProperty.get(promise); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| if (!process.emit('unhandledRejection', reason, promise)) { | ||
| // Nobody is listening. | ||
| // TODO(petkaantonov) Take some default action, see #830 | ||
| process.emitWarning('Possibly unhandled promise rejection ' | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we should double emit. Why not just print it, or use the internal warning logic directly? I also definitely don't think this should be "catchable" in the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, the point of this PR was to use @jasnell's idea of treating them as warnings. If we consider them warning and not errors initially I see double benefit: A) It's theoretically sound, since we're not claiming it's an error but a warning (like adding a lot of listeners to an EventEmitter). Note that rejections are pretty rare (like actual exceptions) and unhandled rejections even more so - so the performance penalty here is negligible. By emitting a warning rather than directly printing an error - I think we're leaving a much bigger opportunity for a long term solution: people realize warnings might go away, can be turned off, can be reported to another channel than errors and so on. This does not contradict a "throw on unhandled GC" or another "long term" solution. It does solve the problem of promises being terribly hard for users to debug with the current default which is what people always complain about in StackOverflow and forums. I think it's a reasonable compromise and that we haven't been fair to our users since we landed
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could potentially check the listener count for unhandledRejection first and only emit if it's greater than zero, otherwise emit the warning. This is precisely the kind of thing emitWarning is for. -1 for printing directly.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm happy with I'd quibble that we should probably log this as an
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jasnell what you are describing is exactly the behavior in this PR, emitWarning is only called if there are no
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @chrisdickinson's point makes sense. |
||
| + '(unhandled rejection id: ' + uid +'): ' | ||
| + reason, | ||
| 'UnhandledPromiseRejectionWarning'); | ||
| } else { | ||
| hadListeners = true; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe
let?