Skip to content

Commit

Permalink
promise: better stack traces for --trace-warnings
Browse files Browse the repository at this point in the history
Give better stack traces for `PromiseRejectionHandledWarning`
and `UnhandledPromiseRejectionWarning`s.

For `PromiseRejectionHandledWarning`, when it is likely that there
is an `Error` object generated, it is created early to provide a
proper stack trace.

For `UnhandledPromiseRejectionWarning`, the stack trace of the
underlying error object is used, if possible.

Fixes: #9523
PR-URL: #9525
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Daniel Bevenius <daniel.bevenius@gmail.com>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
  • Loading branch information
addaleax committed Dec 5, 2016
1 parent a6ab607 commit 196d27d
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 2 deletions.
17 changes: 15 additions & 2 deletions lib/internal/process/promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ let lastPromiseId = 1;

exports.setup = setupPromises;

function getAsynchronousRejectionWarningObject(uid) {
return new Error('Promise rejection was handled ' +
`asynchronously (rejection id: ${uid})`);
}

function setupPromises(scheduleMicrotasks) {
process._setupPromises(function(event, promise, reason) {
if (event === promiseRejectEvent.unhandled)
Expand All @@ -31,10 +36,15 @@ function setupPromises(scheduleMicrotasks) {
const uid = promiseToGuidProperty.get(promise);
promiseToGuidProperty.delete(promise);
if (hasBeenNotified === true) {
let warning = null;
if (!process.listenerCount('rejectionHandled')) {
// Generate the warning object early to get a good stack trace.
warning = getAsynchronousRejectionWarningObject(uid);
}
process.nextTick(function() {
if (!process.emit('rejectionHandled', promise)) {
const warning = new Error('Promise rejection was handled ' +
`asynchronously (rejection id: ${uid})`);
if (warning === null)
warning = getAsynchronousRejectionWarningObject(uid);
warning.name = 'PromiseRejectionHandledWarning';
warning.id = uid;
process.emitWarning(warning);
Expand All @@ -50,6 +60,9 @@ function setupPromises(scheduleMicrotasks) {
`(rejection id: ${uid}): ${reason}`);
warning.name = 'UnhandledPromiseRejectionWarning';
warning.id = uid;
if (reason instanceof Error) {
warning.stack = reason.stack;
}
process.emitWarning(warning);
if (!deprecationWarned) {
deprecationWarned = true;
Expand Down
5 changes: 5 additions & 0 deletions test/message/unhandled_promise_trace_warnings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Flags: --trace-warnings
'use strict';
require('../common');
const p = Promise.reject(new Error('This was rejected'));
setImmediate(() => p.catch(() => {}));
31 changes: 31 additions & 0 deletions test/message/unhandled_promise_trace_warnings.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
(node:*) Error: This was rejected
at * (*test*message*unhandled_promise_trace_warnings.js:*)
at *
at *
at *
at *
at *
at *
at *
at *
at *
(node:*) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
at *
at *
at *
at *
at *
at *
at *
at *
at *
(node:*) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
at getAsynchronousRejectionWarningObject (internal/process/promises.js:*)
at rejectionHandled (internal/process/promises.js:*)
at *
at Promise.then (native)
at Promise.catch (native)
at Immediate.setImmediate (*test*message*unhandled_promise_trace_warnings.js:*)
at *
at *
at *

0 comments on commit 196d27d

Please sign in to comment.