diff --git a/doc/api/process.md b/doc/api/process.md index a67752878b285d..afb130054a83e0 100644 --- a/doc/api/process.md +++ b/doc/api/process.md @@ -186,6 +186,12 @@ this, you can either attach a dummy `.catch(() => { })` handler to `resource.loaded`, preventing the `'unhandledRejection'` event from being emitted, or you can use the [`'rejectionHandled'`][] event. +When `'--throw-unhandled-rejection'` option is on, Node process throws +an exception if `unhandledRejection` listener is not exist. + +And `'--trace-unhandled-rejection'` option is on, Node outputs stderr message +an exception if `unhandledRejection` listener is not exist. + ## Event: 'warning' Emitted whenever Node.js emits a process warning. diff --git a/lib/internal/process/promises.js b/lib/internal/process/promises.js index 22f1959784be9a..a765b23035cb5a 100644 --- a/lib/internal/process/promises.js +++ b/lib/internal/process/promises.js @@ -3,6 +3,9 @@ const promiseRejectEvent = process._promiseRejectEvent; const hasBeenNotifiedProperty = new WeakMap(); const pendingUnhandledRejections = []; +const traceUnhandledRejection = process.traceUnhandledRejection; +const throwUnhandledRejection = process.throwUnhandledRejection; +const prefix = `(${process.release.name}:${process.pid}) `; exports.setup = setupPromises; @@ -44,6 +47,18 @@ function setupPromises(scheduleMicrotasks) { if (!process.emit('unhandledRejection', reason, promise)) { // Nobody is listening. // TODO(petkaantonov) Take some default action, see #830 + + if (traceUnhandledRejection) { + if (reason && reason.stack) { + console.error(`${prefix}${reason.stack}`); + } else { + console.error(`${prefix}${reason}`); + } + } + + if (throwUnhandledRejection) { + throw reason; + } } else { hadListeners = true; } diff --git a/src/node.cc b/src/node.cc index e7836fe0a6b5ab..469e5c5ee4cee2 100644 --- a/src/node.cc +++ b/src/node.cc @@ -143,6 +143,8 @@ static bool trace_deprecation = false; static bool throw_deprecation = false; static bool trace_sync_io = false; static bool track_heap_objects = false; +static bool trace_unhandled_rejection = false; +static bool throw_unhandled_rejection = false; static const char* eval_string = nullptr; static unsigned int preload_module_count = 0; static const char** preload_modules = nullptr; @@ -3142,6 +3144,18 @@ void SetupProcessObject(Environment* env, READONLY_PROPERTY(process, "traceDeprecation", True(env->isolate())); } + // --trace-unhandled-rejection + if (trace_unhandled_rejection) { + READONLY_PROPERTY( + process, "traceUnhandledRejection", True(env->isolate())); + } + + // --throw-unhandled-rejection + if (throw_unhandled_rejection) { + READONLY_PROPERTY( + process, "throwUnhandledRejection", True(env->isolate())); + } + // --security-revert flags #define V(code, _, __) \ do { \ @@ -3571,6 +3585,10 @@ static void ParseArgs(int* argc, } else if (strncmp(arg, "--icu-data-dir=", 15) == 0) { icu_data_dir = arg + 15; #endif + } else if (strcmp(arg, "--trace-unhandled-rejection") == 0) { + trace_unhandled_rejection = true; + } else if (strcmp(arg, "--throw-unhandled-rejection") == 0) { + throw_unhandled_rejection = true; } else if (strcmp(arg, "--expose-internals") == 0 || strcmp(arg, "--expose_internals") == 0) { // consumed in js diff --git a/test/parallel/test-throw-default-error-unhandled-rejections.js b/test/parallel/test-throw-default-error-unhandled-rejections.js new file mode 100644 index 00000000000000..ed636abc60276c --- /dev/null +++ b/test/parallel/test-throw-default-error-unhandled-rejections.js @@ -0,0 +1,36 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const spawn = require('child_process').spawn; +const node = process.execPath; + +if (process.argv[2] === 'child') { + const rejectPromise = () => { + return new Promise((resolve, reject) => { + setTimeout(() => reject(new Error('Reject Promise')), 100); + }); + }; + rejectPromise(); +} else { + run('--throw-unhandled-rejection'); +} + +function run(flags) { + const args = [__filename, 'child']; + if (flags) + args.unshift(flags); + + const child = spawn(node, args); + let message = ''; + child.stderr.on('data', (data) => { + message += data; + }); + child.stderr.on('end', common.mustCall(() => { + assert(message.match(/Reject Promise/)); + })); + child.on('exit', common.mustCall((code) => { + assert.strictEqual(code, 1); + })); +} + diff --git a/test/parallel/test-trace-default-error-unhandled-rejections.js b/test/parallel/test-trace-default-error-unhandled-rejections.js new file mode 100644 index 00000000000000..ef1d4d44d11435 --- /dev/null +++ b/test/parallel/test-trace-default-error-unhandled-rejections.js @@ -0,0 +1,37 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const spawn = require('child_process').spawn; +const node = process.execPath; + +if (process.argv[2] === 'child') { + const rejectPromise = () => { + return new Promise((resolve, reject) => { + setTimeout(() => reject(new Error('Reject Promise')), 100); + }); + }; + rejectPromise(); +} else { + run('--trace-unhandled-rejection'); +} + +function run(flags) { + const args = [__filename, 'child']; + if (flags) + args.unshift(flags); + + const child = spawn(node, args); + let errorMessage = ''; + child.stderr.on('data', (data) => { + errorMessage += data; + }); + child.stderr.on('end', common.mustCall(() => { + assert(errorMessage.match(/Reject Promise/)); + })); + child.on('exit', common.mustCall((code) => { + assert.strictEqual(code, 0); + })); +} + + diff --git a/test/parallel/test-trace-null-unhandled-rejections.js b/test/parallel/test-trace-null-unhandled-rejections.js new file mode 100644 index 00000000000000..930b5e6229aff6 --- /dev/null +++ b/test/parallel/test-trace-null-unhandled-rejections.js @@ -0,0 +1,37 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const spawn = require('child_process').spawn; +const node = process.execPath; + +if (process.argv[2] === 'child') { + const rejectPromise = () => { + return new Promise((resolve, reject) => { + setTimeout(() => reject(null), 100); + }); + }; + rejectPromise(); +} else { + run('--trace-unhandled-rejection'); +} + +function run(flags) { + const args = [__filename, 'child']; + if (flags) + args.unshift(flags); + + const child = spawn(node, args); + let message = ''; + child.stderr.on('data', (data) => { + message += data; + }); + child.stderr.on('end', common.mustCall(() => { + assert(message.match(/null/)); + })); + child.on('exit', common.mustCall((code) => { + assert.strictEqual(code, 0); + })); +} + +