Skip to content
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

Promises/better unhandled rejection message #17158

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions lib/internal/process/promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,25 @@ function setupPromises(scheduleMicrotasks) {
}

function emitWarning(uid, reason) {
try {
if (reason instanceof Error) {
process.emitWarning(reason.stack, 'UnhandledPromiseRejectionWarning');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I guess the only downfall to doing it this way is it won't include any additional properties added on to the Error object. Any reason to do this vs using util.inspect()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just that I wasn't aware of util.inspect(). I went with what the rest of the code did in the same function.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Namely, Node doesn't show this information for synchronous errors:

// test.js
var e = new Error("error");
e.foo = "bar";
throw e;
~/test/test.js:3
throw e;
^

Error: error
    at Object.<anonymous> (/Users/benjamin/Downloads/test.js:1:71)
    at Module._compile (module.js:573:30)
    at Object.Module._extensions..js (module.js:584:10)
    at Module.load (module.js:507:32)
    at tryModuleLoad (module.js:470:12)
    at Function.Module._load (module.js:462:3)
    at Function.Module.runMain (module.js:609:10)
    at startup (bootstrap_node.js:158:16)
    at bootstrap_node.js:598:3

} else {
process.emitWarning(
safeToString(reason), 'UnhandledPromiseRejectionWarning'
);
}
} catch (e) {
// ignored
}

const warning = new Error(
`Unhandled promise rejection (rejection id: ${uid}): ` +
safeToString(reason));
'Unhandled promise rejection. This error originated either by ' +
'throwing inside of an async function without a catch block, ' +
'or by rejecting a promise which was not handled with .catch(). ' +
`(rejection id: ${uid})`
);
warning.name = 'UnhandledPromiseRejectionWarning';
warning.id = uid;
try {
if (reason instanceof Error) {
warning.stack = reason.stack;
Expand Down
18 changes: 18 additions & 0 deletions test/message/unhandled_promise_trace_warnings.out
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
(node:*) UnhandledPromiseRejectionWarning: Error: This was rejected
at * (*test*message*unhandled_promise_trace_warnings.js:*)
at *
at *
at *
at *
at *
at *
at *
at *
at *
at *
at *
at *
at *
at *
at *
at *
(node:*) Error: This was rejected
at * (*test*message*unhandled_promise_trace_warnings.js:*)
at *
Expand Down
7 changes: 5 additions & 2 deletions test/parallel/test-promises-unhandled-proxy-rejections.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ const expectedDeprecationWarning = 'Unhandled promise rejections are ' +
'rejections that are not handled will ' +
'terminate the Node.js process with a ' +
'non-zero exit code.';
const expectedPromiseWarning = 'Unhandled promise rejection (rejection id: ' +
'1): [object Object]';
const expectedPromiseWarning = 'Unhandled promise rejection. ' +
'This error originated either by throwing ' +
'inside of an async function without a catch ' +
'block, or by rejecting a promise which was ' +
'not handled with .catch(). (rejection id: 1)';

function throwErr() {
throw new Error('Error from proxy');
Expand Down
13 changes: 10 additions & 3 deletions test/parallel/test-promises-unhandled-symbol-rejections.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
'use strict';
const common = require('../common');

const expectedValueWarning = 'Symbol()';
const expectedDeprecationWarning = '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.';
const expectedPromiseWarning = 'Unhandled promise rejection (rejection id: ' +
'1): Symbol()';
const expectedPromiseWarning = 'Unhandled promise rejection. ' +
'This error originated either by throwing ' +
'inside of an async function without a catch ' +
'block, or by rejecting a promise which was ' +
'not handled with .catch(). (rejection id: 1)';

common.expectWarning({
DeprecationWarning: expectedDeprecationWarning,
UnhandledPromiseRejectionWarning: expectedPromiseWarning,
UnhandledPromiseRejectionWarning: [
expectedPromiseWarning,
expectedValueWarning
],
});

// ensure this doesn't crash
Expand Down
37 changes: 31 additions & 6 deletions test/parallel/test-promises-warning-on-unhandled-rejection.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,43 @@ let b = 0;
process.on('warning', common.mustCall((warning) => {
switch (b++) {
case 0:
assert.strictEqual(warning.name, 'UnhandledPromiseRejectionWarning');
assert(/Unhandled promise rejection/.test(warning.message));
// String rejection error displayed
assert.strictEqual(warning.message, 'This was rejected');
break;
case 1:
assert.strictEqual(warning.name, 'DeprecationWarning');
// Warning about rejection not being handled (will be next tick)
assert.strictEqual(warning.name, 'UnhandledPromiseRejectionWarning');
assert(
/Unhandled promise rejection/.test(warning.message),
'Expected warning message to contain "Unhandled promise rejection" ' +
'but did not. Had "' + warning.message + '" instead.'
);
break;
case 2:
// One time deprecation warning, first unhandled rejection
assert.strictEqual(warning.name, 'DeprecationWarning');
break;
case 3:
// Number rejection error displayed. Note it's been stringified
assert.strictEqual(warning.message, '42');
break;
case 4:
// Unhandled rejection warning (won't be handled next tick)
assert.strictEqual(warning.name, 'UnhandledPromiseRejectionWarning');
assert(
/Unhandled promise rejection/.test(warning.message),
'Expected warning message to contain "Unhandled promise rejection" ' +
'but did not. Had "' + warning.message + '" instead.'
);
break;
case 5:
// Rejection handled asynchronously.
assert.strictEqual(warning.name, 'PromiseRejectionHandledWarning');
assert(/Promise rejection was handled asynchronously/
.test(warning.message));
}
}, 3));
}, 6));

const p = Promise.reject('This was rejected');
setImmediate(common.mustCall(() => p.catch(() => {})));
const p = Promise.reject('This was rejected'); // Reject with a string
setImmediate(common.mustCall(() => p.catch(() => { })));
Promise.reject(42); // Reject with a number