diff options
25 files changed, 376 insertions, 50 deletions
diff --git a/doc/api/cli.md b/doc/api/cli.md index 60d75a6c04..f56eb981a9 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -577,6 +577,23 @@ added: v2.4.0 Track heap object allocations for heap snapshots. +### `--unhandled-rejections=mode` +<!-- YAML +added: REPLACEME +--> + +By default all unhandled rejections trigger a warning plus a deprecation warning +for the very first unhandled rejection in case no [`unhandledRejection`][] hook +is used. + +Using this flag allows to change what should happen when an unhandled rejection +occurs. One of three modes can be chosen: + +* `strict`: Raise the unhandled rejection as an uncaught exception. +* `warn`: Always trigger a warning, no matter if the [`unhandledRejection`][] + hook is set or not but do not print the deprecation warning. +* `none`: Silence all warnings. + ### `--use-bundled-ca`, `--use-openssl-ca` <!-- YAML added: v6.11.0 @@ -798,6 +815,7 @@ Node.js options that are allowed are: - `--trace-sync-io` - `--trace-warnings` - `--track-heap-objects` +- `--unhandled-rejections` - `--use-bundled-ca` - `--use-openssl-ca` - `--v8-pool-size` @@ -966,6 +984,7 @@ greater than `4` (its current default value). For more information, see the [`process.setUncaughtExceptionCaptureCallback()`]: process.html#process_process_setuncaughtexceptioncapturecallback_fn [`tls.DEFAULT_MAX_VERSION`]: tls.html#tls_tls_default_max_version [`tls.DEFAULT_MIN_VERSION`]: tls.html#tls_tls_default_min_version +[`unhandledRejection`]: process.html#process_event_unhandledrejection [Chrome DevTools Protocol]: https://chromedevtools.github.io/devtools-protocol/ [REPL]: repl.html [ScriptCoverage]: https://chromedevtools.github.io/devtools-protocol/tot/Profiler#type-ScriptCoverage diff --git a/doc/api/process.md b/doc/api/process.md index 5eabb6fad1..95f57f6932 100644 --- a/doc/api/process.md +++ b/doc/api/process.md @@ -205,8 +205,17 @@ most convenient for scripts). ### Event: 'uncaughtException' <!-- YAML added: v0.1.18 +changes: + - version: REPLACEME + pr-url: https://github.com/nodejs/node/pull/26599 + description: Added the `origin` argument. --> +* `err` {Error} The uncaught exception. +* `origin` {string} Indicates if the exception originates from an unhandled + rejection or from synchronous errors. Can either be `'uncaughtException'` or + `'unhandledRejection'`. + The `'uncaughtException'` event is emitted when an uncaught JavaScript exception bubbles all the way back to the event loop. By default, Node.js handles such exceptions by printing the stack trace to `stderr` and exiting @@ -217,12 +226,13 @@ behavior. Alternatively, change the [`process.exitCode`][] in the provided exit code. Otherwise, in the presence of such handler the process will exit with 0. -The listener function is called with the `Error` object passed as the only -argument. - ```js -process.on('uncaughtException', (err) => { - fs.writeSync(1, `Caught exception: ${err}\n`); +process.on('uncaughtException', (err, origin) => { + fs.writeSync( + process.stderr.fd, + `Caught exception: ${err}\n` + + `Exception origin: ${origin}` + ); }); setTimeout(() => { @@ -274,6 +284,10 @@ changes: a process warning. --> +* `reason` {Error|any} The object with which the promise was rejected + (typically an [`Error`][] object). +* `promise` {Promise} The rejected promise. + The `'unhandledRejection'` event is emitted whenever a `Promise` is rejected and no error handler is attached to the promise within a turn of the event loop. When programming with Promises, exceptions are encapsulated as "rejected @@ -282,15 +296,9 @@ are propagated through a `Promise` chain. The `'unhandledRejection'` event is useful for detecting and keeping track of promises that were rejected whose rejections have not yet been handled. -The listener function is called with the following arguments: - -* `reason` {Error|any} The object with which the promise was rejected - (typically an [`Error`][] object). -* `p` the `Promise` that was rejected. - ```js -process.on('unhandledRejection', (reason, p) => { - console.log('Unhandled Rejection at:', p, 'reason:', reason); +process.on('unhandledRejection', (reason, promise) => { + console.log('Unhandled Rejection at:', promise, 'reason:', reason); // Application specific logging, throwing an error, or other logic here }); @@ -317,7 +325,7 @@ as would typically be the case for other `'unhandledRejection'` events. To address such failures, a non-operational [`.catch(() => { })`][`promise.catch()`] handler may be attached to `resource.loaded`, which would prevent the `'unhandledRejection'` event from -being emitted. Alternatively, the [`'rejectionHandled'`][] event may be used. +being emitted. ### Event: 'warning' <!-- YAML @@ -2282,7 +2290,6 @@ cases: [`'exit'`]: #process_event_exit [`'message'`]: child_process.html#child_process_event_message -[`'rejectionHandled'`]: #process_event_rejectionhandled [`'uncaughtException'`]: #process_event_uncaughtexception [`ChildProcess.disconnect()`]: child_process.html#child_process_subprocess_disconnect [`ChildProcess.send()`]: child_process.html#child_process_subprocess_send_message_sendhandle_options_callback diff --git a/doc/node.1 b/doc/node.1 index 0fccabc4bc..e2b52b9e87 100644 --- a/doc/node.1 +++ b/doc/node.1 @@ -292,6 +292,9 @@ Print stack traces for process warnings (including deprecations). .It Fl -track-heap-objects Track heap object allocations for heap snapshots. . +.It Fl --unhandled-rejections=mode +Define the behavior for unhandled rejections. Can be one of `strict` (raise an error), `warn` (enforce warnings) or `none` (silence warnings). +. .It Fl -use-bundled-ca , Fl -use-openssl-ca Use bundled Mozilla CA store as supplied by current Node.js version or use OpenSSL's default CA store. The default store is selectable at build-time. diff --git a/lib/internal/main/worker_thread.js b/lib/internal/main/worker_thread.js index 0e463dc935..f290a4acb2 100644 --- a/lib/internal/main/worker_thread.js +++ b/lib/internal/main/worker_thread.js @@ -137,11 +137,11 @@ port.on('message', (message) => { }); // Overwrite fatalException -process._fatalException = (error) => { +process._fatalException = (error, fromPromise) => { debug(`[${threadId}] gets fatal exception`); let caught = false; try { - caught = originalFatalException.call(this, error); + caught = originalFatalException.call(this, error, fromPromise); } catch (e) { error = e; } diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 5d0e777774..6d14c56db5 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -821,7 +821,10 @@ Module.runMain = function() { return loader.import(pathToFileURL(process.argv[1]).pathname); }) .catch((e) => { - internalBinding('task_queue').triggerFatalException(e); + internalBinding('task_queue').triggerFatalException( + e, + true /* fromPromise */ + ); }); // Handle any nextTicks added in the first tick of the program process._tickCallback(); diff --git a/lib/internal/process/execution.js b/lib/internal/process/execution.js index 5712b80eaf..7b651ba5d1 100644 --- a/lib/internal/process/execution.js +++ b/lib/internal/process/execution.js @@ -117,7 +117,7 @@ function noop() {} // before calling into process._fatalException, or this function should // take extra care of the async hooks before it schedules a setImmediate. function createFatalException() { - return (er) => { + return (er, fromPromise) => { // It's possible that defaultTriggerAsyncId was set for a constructor // call that threw and was never cleared. So clear it now. clearDefaultTriggerAsyncId(); @@ -138,9 +138,10 @@ function createFatalException() { } catch {} // Ignore the exception. Diagnostic reporting is unavailable. } + const type = fromPromise ? 'unhandledRejection' : 'uncaughtException'; if (exceptionHandlerState.captureFn !== null) { exceptionHandlerState.captureFn(er); - } else if (!process.emit('uncaughtException', er)) { + } else if (!process.emit('uncaughtException', er, type)) { // If someone handled it, then great. otherwise, die in C++ land // since that means that we'll exit the process, emit the 'exit' event. try { diff --git a/lib/internal/process/promises.js b/lib/internal/process/promises.js index f3e118e2ce..95161ac7a8 100644 --- a/lib/internal/process/promises.js +++ b/lib/internal/process/promises.js @@ -1,6 +1,10 @@ 'use strict'; -const { safeToString } = internalBinding('util'); +const { Object } = primordials; + +const { + safeToString +} = internalBinding('util'); const { tickInfo, promiseRejectEvents: { @@ -9,7 +13,8 @@ const { kPromiseResolveAfterResolved, kPromiseRejectAfterResolved }, - setPromiseRejectCallback + setPromiseRejectCallback, + triggerFatalException } = internalBinding('task_queue'); // *Must* match Environment::TickInfo::Fields in src/env.h. @@ -20,6 +25,15 @@ const pendingUnhandledRejections = []; const asyncHandledRejections = []; let lastPromiseId = 0; +const states = { + none: 0, + warn: 1, + strict: 2, + default: 3 +}; + +let state; + function setHasRejectionToWarn(value) { tickInfo[kHasRejectionToWarn] = value ? 1 : 0; } @@ -29,6 +43,10 @@ function hasRejectionToWarn() { } function promiseRejectHandler(type, promise, reason) { + if (state === undefined) { + const { getOptionValue } = require('internal/options'); + state = states[getOptionValue('--unhandled-rejections') || 'default']; + } switch (type) { case kPromiseRejectWithNoHandler: unhandledRejection(promise, reason); @@ -59,6 +77,7 @@ function unhandledRejection(promise, reason) { uid: ++lastPromiseId, warned: false }); + // This causes the promise to be referenced at least for one tick. pendingUnhandledRejections.push(promise); setHasRejectionToWarn(true); } @@ -85,14 +104,16 @@ function handledRejection(promise) { const unhandledRejectionErrName = 'UnhandledPromiseRejectionWarning'; function emitWarning(uid, reason) { - // eslint-disable-next-line no-restricted-syntax - const warning = new Error( + if (state === states.none) { + return; + } + const warning = getError( + unhandledRejectionErrName, '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})` + '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 = unhandledRejectionErrName; try { if (reason instanceof Error) { warning.stack = reason.stack; @@ -108,7 +129,7 @@ function emitWarning(uid, reason) { let deprecationWarned = false; function emitDeprecationWarning() { - if (!deprecationWarned) { + if (state === states.default && !deprecationWarned) { deprecationWarned = true; process.emitWarning( 'Unhandled promise rejections are deprecated. In the future, ' + @@ -133,18 +154,57 @@ function processPromiseRejections() { while (len--) { const promise = pendingUnhandledRejections.shift(); const promiseInfo = maybeUnhandledPromises.get(promise); - if (promiseInfo !== undefined) { - promiseInfo.warned = true; - const { reason, uid } = promiseInfo; - if (!process.emit('unhandledRejection', reason, promise)) { - emitWarning(uid, reason); - } - maybeScheduledTicks = true; + if (promiseInfo === undefined) { + continue; + } + promiseInfo.warned = true; + const { reason, uid } = promiseInfo; + if (state === states.strict) { + fatalException(reason); } + if (!process.emit('unhandledRejection', reason, promise) || + // Always warn in case the user requested it. + state === states.warn) { + emitWarning(uid, reason); + } + maybeScheduledTicks = true; } return maybeScheduledTicks || pendingUnhandledRejections.length !== 0; } +function getError(name, message) { + // Reset the stack to prevent any overhead. + const tmp = Error.stackTraceLimit; + Error.stackTraceLimit = 0; + // eslint-disable-next-line no-restricted-syntax + const err = new Error(message); + Error.stackTraceLimit = tmp; + Object.defineProperty(err, 'name', { + value: name, + enumerable: false, + writable: true, + configurable: true, + }); + return err; +} + +function fatalException(reason) { + let err; + if (reason instanceof Error) { + err = reason; + } else { + err = getError( + 'UnhandledPromiseRejection', + '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().' + + ` The promise rejected with the reason "${safeToString(reason)}".` + ); + err.code = 'ERR_UNHANDLED_REJECTION'; + } + triggerFatalException(err, true /* fromPromise */); +} + function listenForRejections() { setPromiseRejectCallback(promiseRejectHandler); } diff --git a/lib/internal/process/task_queues.js b/lib/internal/process/task_queues.js index bc65d25307..12e34b7ff7 100644 --- a/lib/internal/process/task_queues.js +++ b/lib/internal/process/task_queues.js @@ -155,11 +155,11 @@ function runMicrotask() { try { callback(); } catch (error) { - // TODO(devsnek) remove this if + // TODO(devsnek): Remove this if // https://bugs.chromium.org/p/v8/issues/detail?id=8326 // is resolved such that V8 triggers the fatal exception - // handler for microtasks - triggerFatalException(error); + // handler for microtasks. + triggerFatalException(error, false /* fromPromise */); } finally { this.emitDestroy(); } diff --git a/src/node_errors.cc b/src/node_errors.cc index 3c04152974..83dbfc611f 100644 --- a/src/node_errors.cc +++ b/src/node_errors.cc @@ -10,6 +10,7 @@ namespace node { using errors::TryCatchScope; +using v8::Boolean; using v8::Context; using v8::Exception; using v8::Function; @@ -771,7 +772,8 @@ void DecorateErrorStack(Environment* env, void FatalException(Isolate* isolate, Local<Value> error, - Local<Message> message) { + Local<Message> message, + bool from_promise) { CHECK(!error.IsEmpty()); HandleScope scope(isolate); @@ -794,9 +796,12 @@ void FatalException(Isolate* isolate, // Do not call FatalException when _fatalException handler throws fatal_try_catch.SetVerbose(false); + Local<Value> argv[2] = { error, + Boolean::New(env->isolate(), from_promise) }; + // This will return true if the JS layer handled it, false otherwise MaybeLocal<Value> caught = fatal_exception_function.As<Function>()->Call( - env->context(), process_object, 1, &error); + env->context(), process_object, arraysize(argv), argv); if (fatal_try_catch.HasTerminated()) return; @@ -821,4 +826,10 @@ void FatalException(Isolate* isolate, } } +void FatalException(Isolate* isolate, + Local<Value> error, + Local<Message> message) { + FatalException(isolate, error, message, false /* from_promise */); +} + } // namespace node diff --git a/src/node_errors.h b/src/node_errors.h index b04a347f1e..c27f4b36fa 100644 --- a/src/node_errors.h +++ b/src/node_errors.h @@ -39,6 +39,11 @@ void FatalException(v8::Isolate* isolate, Local<Value> error, Local<Message> message); +void FatalException(v8::Isolate* isolate, + Local<Value> error, + Local<Message> message, + bool from_promise); + // Helpers to construct errors similar to the ones provided by // lib/internal/errors.js. // Example: with `V(ERR_INVALID_ARG_TYPE, TypeError)`, there will be diff --git a/src/node_options.cc b/src/node_options.cc index c307c66a62..cb490dad75 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -141,6 +141,13 @@ void EnvironmentOptions::CheckOptions(std::vector<std::string>* errors) { errors->push_back("invalid value for --http-parser"); } + if (!unhandled_rejections.empty() && + unhandled_rejections != "strict" && + unhandled_rejections != "warn" && + unhandled_rejections != "none") { + errors->push_back("invalid value for --unhandled-rejections"); + } + #if HAVE_INSPECTOR debug_options_.CheckOptions(errors); #endif // HAVE_INSPECTOR @@ -287,6 +294,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { "custom loader", &EnvironmentOptions::userland_loader, kAllowedInEnvironment); + AddOption("--entry-type", + "set module type name of the entry point", + &EnvironmentOptions::module_type, + kAllowedInEnvironment); AddOption("--es-module-specifier-resolution", "Select extension resolution algorithm for es modules; " "either 'explicit' (default) or 'node'", @@ -342,9 +353,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { "show stack traces on process warnings", &EnvironmentOptions::trace_warnings, kAllowedInEnvironment); - AddOption("--entry-type", - "set module type name of the entry point", - &EnvironmentOptions::module_type, + AddOption("--unhandled-rejections", + "define unhandled rejections behavior. Options are 'strict' (raise " + "an error), 'warn' (enforce warnings) or 'none' (silence warnings)", + &EnvironmentOptions::unhandled_rejections, kAllowedInEnvironment); AddOption("--check", diff --git a/src/node_options.h b/src/node_options.h index 59baea562a..0fb480b397 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -114,6 +114,7 @@ class EnvironmentOptions : public Options { bool trace_deprecation = false; bool trace_sync_io = false; bool trace_warnings = false; + std::string unhandled_rejections; std::string userland_loader; bool syntax_check_only = false; diff --git a/src/node_task_queue.cc b/src/node_task_queue.cc index b832a66d8b..b125f5d01e 100644 --- a/src/node_task_queue.cc +++ b/src/node_task_queue.cc @@ -116,7 +116,8 @@ static void TriggerFatalException(const FunctionCallbackInfo<Value>& args) { ReportException(env, exception, message); Abort(); } - FatalException(isolate, exception, message); + bool from_promise = args[1]->IsTrue(); + FatalException(isolate, exception, message, from_promise); } static void Initialize(Local<Object> target, diff --git a/test/message/promise_always_throw_unhandled.js b/test/message/promise_always_throw_unhandled.js new file mode 100644 index 0000000000..d9128f34a5 --- /dev/null +++ b/test/message/promise_always_throw_unhandled.js @@ -0,0 +1,16 @@ +// Flags: --unhandled-rejections=strict +'use strict'; + +require('../common'); + +// Check that the process will exit on the first unhandled rejection in case the +// unhandled rejections mode is set to `'strict'`. + +const ref1 = new Promise(() => { + throw new Error('One'); +}); + +const ref2 = Promise.reject(new Error('Two')); + +// Keep the event loop alive to actually detect the unhandled rejection. +setTimeout(() => console.log(ref1, ref2), 1000); diff --git a/test/message/promise_always_throw_unhandled.out b/test/message/promise_always_throw_unhandled.out new file mode 100644 index 0000000000..8d03026856 --- /dev/null +++ b/test/message/promise_always_throw_unhandled.out @@ -0,0 +1,13 @@ +*promise_always_throw_unhandled.js:* + throw new Error('One'); + ^ +Error: One + at *promise_always_throw_unhandled.js:*:* + at new Promise (<anonymous>) + at Object.<anonymous> (*promise_always_throw_unhandled.js:*:*) + at * + at * + at * + at * + at * + at * diff --git a/test/parallel/test-cli-node-options.js b/test/parallel/test-cli-node-options.js index 5fd6c1f2c9..d3d1938bf8 100644 --- a/test/parallel/test-cli-node-options.js +++ b/test/parallel/test-cli-node-options.js @@ -34,6 +34,7 @@ expect('--trace-event-file-pattern {pid}-${rotation}.trace_events', 'B\n'); // eslint-disable-next-line no-template-curly-in-string expect('--trace-event-file-pattern {pid}-${rotation}.trace_events ' + '--trace-event-categories node.async_hooks', 'B\n'); +expect('--unhandled-rejections=none', 'B\n'); if (!common.isWindows) { expect('--perf-basic-prof', 'B\n'); diff --git a/test/parallel/test-next-tick-errors.js b/test/parallel/test-next-tick-errors.js index 51ed2524a0..bf142bb351 100644 --- a/test/parallel/test-next-tick-errors.js +++ b/test/parallel/test-next-tick-errors.js @@ -61,7 +61,9 @@ testNextTickWith('str'); testNextTickWith({}); testNextTickWith([]); -process.on('uncaughtException', function() { +process.on('uncaughtException', function(err, errorOrigin) { + assert.strictEqual(errorOrigin, 'uncaughtException'); + if (!exceptionHandled) { exceptionHandled = true; order.push('B'); diff --git a/test/parallel/test-promise-unhandled-error.js b/test/parallel/test-promise-unhandled-error.js new file mode 100644 index 0000000000..bb906fdcbe --- /dev/null +++ b/test/parallel/test-promise-unhandled-error.js @@ -0,0 +1,54 @@ +// Flags: --unhandled-rejections=strict +'use strict'; + +const common = require('../common'); +const Countdown = require('../common/countdown'); +const assert = require('assert'); + +common.disableCrashOnUnhandledRejection(); + +// Verify that unhandled rejections always trigger uncaught exceptions instead +// of triggering unhandled rejections. + +const err1 = new Error('One'); +const err2 = new Error( + '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(). The promise rejected with the' + + ' reason "null".' +); +err2.code = 'ERR_UNHANDLED_REJECTION'; +Object.defineProperty(err2, 'name', { + value: 'UnhandledPromiseRejection', + writable: true, + configurable: true +}); + +const errors = [err1, err2]; +const identical = [true, false]; + +const ref = new Promise(() => { + throw err1; +}); +// Explicitly reject `null`. +Promise.reject(null); + +process.on('warning', common.mustNotCall('warning')); +process.on('unhandledRejection', common.mustCall(2)); +process.on('rejectionHandled', common.mustNotCall('rejectionHandled')); +process.on('exit', assert.strictEqual.bind(null, 0)); + +const timer = setTimeout(() => console.log(ref), 1000); + +const counter = new Countdown(2, () => { + clearTimeout(timer); +}); + +process.on('uncaughtException', common.mustCall((err, origin) => { + counter.dec(); + assert.strictEqual(origin, 'unhandledRejection', err); + const knownError = errors.shift(); + assert.deepStrictEqual(err, knownError); + // Check if the errors are reference equal. + assert(identical.shift() ? err === knownError : err !== knownError); +}, 2)); diff --git a/test/parallel/test-promise-unhandled-flag.js b/test/parallel/test-promise-unhandled-flag.js new file mode 100644 index 0000000000..40a9030917 --- /dev/null +++ b/test/parallel/test-promise-unhandled-flag.js @@ -0,0 +1,16 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); +const cp = require('child_process'); + +// Verify that a faulty environment variable throws on bootstrapping. +// Therefore we do not need any special handling for the child process. +const child = cp.spawnSync( + process.execPath, + ['--unhandled-rejections=foobar', __filename] +); + +assert.strictEqual(child.stdout.toString(), ''); +assert(child.stderr.includes( + 'invalid value for --unhandled-rejections'), child.stderr); diff --git a/test/parallel/test-promise-unhandled-silent-no-hook.js b/test/parallel/test-promise-unhandled-silent-no-hook.js new file mode 100644 index 0000000000..db4f7a8471 --- /dev/null +++ b/test/parallel/test-promise-unhandled-silent-no-hook.js @@ -0,0 +1,22 @@ +// Flags: --unhandled-rejections=none +'use strict'; + +const common = require('../common'); +const assert = require('assert'); + +common.disableCrashOnUnhandledRejection(); + +// Verify that ignoring unhandled rejection works fine and that no warning is +// logged even though there is no unhandledRejection hook attached. + +new Promise(() => { + throw new Error('One'); +}); + +Promise.reject('test'); + +process.on('warning', common.mustNotCall('warning')); +process.on('uncaughtException', common.mustNotCall('uncaughtException')); +process.on('exit', assert.strictEqual.bind(null, 0)); + +setTimeout(common.mustCall(), 2); diff --git a/test/parallel/test-promise-unhandled-silent.js b/test/parallel/test-promise-unhandled-silent.js new file mode 100644 index 0000000000..c9ccc0118e --- /dev/null +++ b/test/parallel/test-promise-unhandled-silent.js @@ -0,0 +1,23 @@ +// Flags: --unhandled-rejections=none +'use strict'; + +const common = require('../common'); + +common.disableCrashOnUnhandledRejection(); + +// Verify that ignoring unhandled rejection works fine and that no warning is +// logged. + +new Promise(() => { + throw new Error('One'); +}); + +Promise.reject('test'); + +process.on('warning', common.mustNotCall('warning')); +process.on('uncaughtException', common.mustNotCall('uncaughtException')); +process.on('rejectionHandled', common.mustNotCall('rejectionHandled')); + +process.on('unhandledRejection', common.mustCall(2)); + +setTimeout(common.mustCall(), 2); diff --git a/test/parallel/test-promise-unhandled-warn-no-hook.js b/test/parallel/test-promise-unhandled-warn-no-hook.js new file mode 100644 index 0000000000..91784448ad --- /dev/null +++ b/test/parallel/test-promise-unhandled-warn-no-hook.js @@ -0,0 +1,23 @@ +// Flags: --unhandled-rejections=warn +'use strict'; + +const common = require('../common'); + +common.disableCrashOnUnhandledRejection(); + +// Verify that ignoring unhandled rejection works fine and that no warning is +// logged. + +new Promise(() => { + throw new Error('One'); +}); + +Promise.reject('test'); + +// Unhandled rejections trigger two warning per rejection. One is the rejection +// reason and the other is a note where this warning is coming from. +process.on('warning', common.mustCall(4)); +process.on('uncaughtException', common.mustNotCall('uncaughtException')); +process.on('rejectionHandled', common.mustNotCall('rejectionHandled')); + +setTimeout(common.mustCall(), 2); diff --git a/test/parallel/test-promise-unhandled-warn.js b/test/parallel/test-promise-unhandled-warn.js new file mode 100644 index 0000000000..62ddc69d8c --- /dev/null +++ b/test/parallel/test-promise-unhandled-warn.js @@ -0,0 +1,28 @@ +// Flags: --unhandled-rejections=warn +'use strict'; + +const common = require('../common'); + +common.disableCrashOnUnhandledRejection(); + +// Verify that ignoring unhandled rejection works fine and that no warning is +// logged. + +new Promise(() => { + throw new Error('One'); +}); + +Promise.reject('test'); + +// Unhandled rejections trigger two warning per rejection. One is the rejection +// reason and the other is a note where this warning is coming from. +process.on('warning', common.mustCall(4)); +process.on('uncaughtException', common.mustNotCall('uncaughtException')); +process.on('rejectionHandled', common.mustCall(2)); + +process.on('unhandledRejection', (reason, promise) => { + // Handle promises but still warn! + promise.catch(() => {}); +}); + +setTimeout(common.mustCall(), 2); diff --git a/test/parallel/test-timers-immediate-queue-throw.js b/test/parallel/test-timers-immediate-queue-throw.js index 9929b27ab2..477f0388f6 100644 --- a/test/parallel/test-timers-immediate-queue-throw.js +++ b/test/parallel/test-timers-immediate-queue-throw.js @@ -23,8 +23,11 @@ const errObj = { message: 'setImmediate Err' }; -process.once('uncaughtException', common.expectsError(errObj)); -process.once('uncaughtException', () => assert.strictEqual(stage, 0)); +process.once('uncaughtException', common.mustCall((err, errorOrigin) => { + assert.strictEqual(errorOrigin, 'uncaughtException'); + assert.strictEqual(stage, 0); + common.expectsError(errObj)(err); +})); const d1 = domain.create(); d1.once('error', common.expectsError(errObj)); diff --git a/test/parallel/test-timers-unref-throw-then-ref.js b/test/parallel/test-timers-unref-throw-then-ref.js index d3ae27e835..1dd5fdd0ad 100644 --- a/test/parallel/test-timers-unref-throw-then-ref.js +++ b/test/parallel/test-timers-unref-throw-then-ref.js @@ -2,8 +2,10 @@ const common = require('../common'); const assert = require('assert'); -process.once('uncaughtException', common.expectsError({ - message: 'Timeout Error' +process.once('uncaughtException', common.mustCall((err) => { + common.expectsError({ + message: 'Timeout Error' + })(err); })); let called = false; |