Skip to content

Commit

Permalink
promise: warn on unhandled rejections
Browse files Browse the repository at this point in the history
Log unhandled promise rejections with a guid and emit
a process warning. When rejection is eventually handled,
emit a secondary warning.

PR-URL: nodejs#8223
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Chris Dickinson <christopher.s.dickinson@gmail.com>
  • Loading branch information
benjamingr authored and jasnell committed Aug 31, 2016
1 parent 862610f commit 180867d
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 6 deletions.
27 changes: 21 additions & 6 deletions lib/internal/process/promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

const promiseRejectEvent = process._promiseRejectEvent;
const hasBeenNotifiedProperty = new WeakMap();
const promiseToGuidProperty = new WeakMap();
const pendingUnhandledRejections = [];
let lastPromiseId = 1;

exports.setup = setupPromises;

Expand All @@ -18,32 +20,45 @@ 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)) {
const warning = new Error('Promise rejection was handled ' +
`asynchronously (rejection id: ${uid})`);
warning.name = 'PromiseRejectionHandledWarning';
warning.id = uid;
process.emitWarning(warning);
}
});
}

}
}

function emitPendingUnhandledRejections() {
var hadListeners = false;
let hadListeners = false;
while (pendingUnhandledRejections.length > 0) {
var promise = pendingUnhandledRejections.shift();
var reason = pendingUnhandledRejections.shift();
const promise = pendingUnhandledRejections.shift();
const reason = pendingUnhandledRejections.shift();
if (hasBeenNotifiedProperty.get(promise) === false) {
hasBeenNotifiedProperty.set(promise, true);
const uid = promiseToGuidProperty.get(promise);
if (!process.emit('unhandledRejection', reason, promise)) {
// Nobody is listening.
// TODO(petkaantonov) Take some default action, see #830
const warning = new Error('Unhandled promise rejection ' +
`(rejection id: ${uid}): ${reason}`);
warning.name = 'UnhandledPromiseRejectionWarning';
warning.id = uid;
process.emitWarning(warning);
} else {
hadListeners = true;
}
Expand Down
26 changes: 26 additions & 0 deletions test/parallel/test-promises-warning-on-unhandled-rejection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Flags: --no-warnings
'use strict';

// Test that warnings are emitted when a Promise experiences an uncaught
// rejection, and then again if the rejection is handled later on.

const common = require('../common');
const assert = require('assert');

var b = 0;

process.on('warning', common.mustCall((warning) => {
switch (b++) {
case 0:
assert.strictEqual(warning.name, 'UnhandledPromiseRejectionWarning');
assert(/Unhandled promise rejection/.test(warning.message));
break;
case 1:
assert.strictEqual(warning.name, 'PromiseRejectionHandledWarning');
assert(/Promise rejection was handled asynchronously/
.test(warning.message));
}
}, 2));

const p = Promise.reject('This was rejected');
setImmediate(common.mustCall(() => p.catch(() => {})));

0 comments on commit 180867d

Please sign in to comment.