Skip to content
Open
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
6 changes: 3 additions & 3 deletions doc/api/deprecations.md
Original file line number Diff line number Diff line change
Expand Up @@ -3099,10 +3099,10 @@ changes:
description: Documentation-only deprecation.
-->

Type: Runtime.
Type: End-of-Life.

This event was deprecated because it did not work with V8 promise combinators
which diminished its usefulness.
This event was deprecated and removed because it did not work with V8 promise
combinators which diminished its usefulness.

### DEP0161: `process._getActiveRequests()` and `process._getActiveHandles()`

Expand Down
90 changes: 0 additions & 90 deletions doc/api/process.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,95 +176,6 @@ process, the `message` argument can contain data that JSON is not able
to represent.
See [Advanced serialization for `child_process`][] for more details.

### Event: `'multipleResolves'`

<!-- YAML
added: v10.12.0
deprecated:
- v17.6.0
- v16.15.0
-->

> Stability: 0 - Deprecated

* `type` {string} The resolution type. One of `'resolve'` or `'reject'`.
* `promise` {Promise} The promise that resolved or rejected more than once.
* `value` {any} The value with which the promise was either resolved or
rejected after the original resolve.

The `'multipleResolves'` event is emitted whenever a `Promise` has been either:

* Resolved more than once.
* Rejected more than once.
* Rejected after resolve.
* Resolved after reject.

This is useful for tracking potential errors in an application while using the
`Promise` constructor, as multiple resolutions are silently swallowed. However,
the occurrence of this event does not necessarily indicate an error. For
example, [`Promise.race()`][] can trigger a `'multipleResolves'` event.

Because of the unreliability of the event in cases like the
[`Promise.race()`][] example above it has been deprecated.

```mjs
import process from 'node:process';

process.on('multipleResolves', (type, promise, reason) => {
console.error(type, promise, reason);
setImmediate(() => process.exit(1));
});

async function main() {
try {
return await new Promise((resolve, reject) => {
resolve('First call');
resolve('Swallowed resolve');
reject(new Error('Swallowed reject'));
});
} catch {
throw new Error('Failed');
}
}

main().then(console.log);
// resolve: Promise { 'First call' } 'Swallowed resolve'
// reject: Promise { 'First call' } Error: Swallowed reject
// at Promise (*)
// at new Promise (<anonymous>)
// at main (*)
// First call
```

```cjs
const process = require('node:process');

process.on('multipleResolves', (type, promise, reason) => {
console.error(type, promise, reason);
setImmediate(() => process.exit(1));
});

async function main() {
try {
return await new Promise((resolve, reject) => {
resolve('First call');
resolve('Swallowed resolve');
reject(new Error('Swallowed reject'));
});
} catch {
throw new Error('Failed');
}
}

main().then(console.log);
// resolve: Promise { 'First call' } 'Swallowed resolve'
// reject: Promise { 'First call' } Error: Swallowed reject
// at Promise (*)
// at new Promise (<anonymous>)
// at main (*)
// First call
```

### Event: `'rejectionHandled'`

<!-- YAML
Expand Down Expand Up @@ -3856,7 +3767,6 @@ cases:
[`Error`]: errors.md#class-error
[`EventEmitter`]: events.md#class-eventemitter
[`NODE_OPTIONS`]: cli.md#node_optionsoptions
[`Promise.race()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race
[`Worker`]: worker_threads.md#class-worker
[`Worker` constructor]: worker_threads.md#new-workerfilename-options
[`console.error()`]: console.md#consoleerrordata-args
Expand Down
27 changes: 2 additions & 25 deletions lib/internal/process/promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,10 @@ const {
promiseRejectEvents: {
kPromiseRejectWithNoHandler,
kPromiseHandlerAddedAfterReject,
kPromiseResolveAfterResolved,
kPromiseRejectAfterResolved
},
setPromiseRejectCallback
} = internalBinding('task_queue');

const { deprecate } = require('internal/util');

const {
noSideEffectsToString,
triggerUncaughtException
Expand Down Expand Up @@ -110,37 +106,18 @@ function promiseRejectHandler(type, promise, reason) {
if (unhandledRejectionsMode === undefined) {
unhandledRejectionsMode = getUnhandledRejectionsMode();
}
// kPromiseRejectAfterResolved and kPromiseResolveAfterResolved are
// filtered out in C++ (src/node_task_queue.cc) and never reach JS.
switch (type) {
case kPromiseRejectWithNoHandler:
unhandledRejection(promise, reason);
break;
case kPromiseHandlerAddedAfterReject:
handledRejection(promise);
break;
case kPromiseResolveAfterResolved:
resolveError('resolve', promise, reason);
break;
case kPromiseRejectAfterResolved:
resolveError('reject', promise, reason);
break;
}
}

const multipleResolvesDeprecate = deprecate(
() => {},
'The multipleResolves event has been deprecated.',
'DEP0160'
);
function resolveError(type, promise, reason) {
// We have to wrap this in a next tick. Otherwise the error could be caught by
// the executed promise.
process.nextTick(() => {
if (process.emit('multipleResolves', type, promise, reason)) {
multipleResolvesDeprecate();
}
});
}

function unhandledRejection(promise, reason) {
const emit = (reason, promise, promiseInfo) => {
if (promiseInfo.domain) {
Expand Down
14 changes: 5 additions & 9 deletions src/node_task_queue.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Just;
using v8::kPromiseHandlerAddedAfterReject;
using v8::kPromiseRejectAfterResolved;
using v8::kPromiseRejectWithNoHandler;
using v8::kPromiseResolveAfterResolved;
using v8::Local;
using v8::Maybe;
using v8::Number;
Expand Down Expand Up @@ -75,7 +73,11 @@ void PromiseRejectCallback(PromiseRejectMessage message) {

Environment* env = Environment::GetCurrent(isolate);

if (env == nullptr || !env->can_call_into_js()) return;
if (env == nullptr || !env->can_call_into_js() ||
event != kPromiseRejectWithNoHandler &&
event != kPromiseHandlerAddedAfterReject) {
return;
}

Local<Function> callback = env->promise_reject_callback();
// The promise is rejected before JS land calls SetPromiseRejectCallback
Expand All @@ -99,10 +101,6 @@ void PromiseRejectCallback(PromiseRejectMessage message) {
"rejections",
"unhandled", unhandledRejections,
"handledAfter", rejectionsHandledAfter);
} else if (event == kPromiseResolveAfterResolved) {
value = message.GetValue();
} else if (event == kPromiseRejectAfterResolved) {
value = message.GetValue();
} else {
return;
}
Expand Down Expand Up @@ -206,8 +204,6 @@ static void Initialize(Local<Object> target,
Local<Object> events = Object::New(isolate);
NODE_DEFINE_CONSTANT(events, kPromiseRejectWithNoHandler);
NODE_DEFINE_CONSTANT(events, kPromiseHandlerAddedAfterReject);
NODE_DEFINE_CONSTANT(events, kPromiseResolveAfterResolved);
NODE_DEFINE_CONSTANT(events, kPromiseRejectAfterResolved);

target->Set(env->context(),
FIXED_ONE_BYTE_STRING(isolate, "promiseRejectEvents"),
Expand Down
1 change: 0 additions & 1 deletion test/parallel/test-events-once.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ async function stopListeningAfterCatchingError() {
} catch (_e) {
err = _e;
}
process.removeAllListeners('multipleResolves');
strictEqual(err, expected);
strictEqual(ee.listenerCount('error'), 0);
strictEqual(ee.listenerCount('myevent'), 0);
Expand Down
58 changes: 0 additions & 58 deletions test/parallel/test-promise-swallowed-event.js

This file was deleted.

2 changes: 0 additions & 2 deletions test/parallel/test-timers-immediate-promisified.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ const exec = promisify(child_process.exec);

assert.strictEqual(setPromiseImmediate, timerPromises.setImmediate);

process.on('multipleResolves', common.mustNotCall());

{
const promise = setPromiseImmediate();
promise.then(common.mustCall((value) => {
Expand Down
2 changes: 0 additions & 2 deletions test/parallel/test-timers-interval-promisified.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ const exec = promisify(child_process.exec);

const { setInterval } = timerPromises;

process.on('multipleResolves', common.mustNotCall());

{
const iterable = setInterval(1, undefined);
const iterator = iterable[Symbol.asyncIterator]();
Expand Down
2 changes: 0 additions & 2 deletions test/parallel/test-timers-timeout-promisified.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ const exec = promisify(child_process.exec);

assert.strictEqual(setPromiseTimeout, timerPromises.setTimeout);

process.on('multipleResolves', common.mustNotCall());

{
const promise = setPromiseTimeout(1);
promise.then(common.mustCall((value) => {
Expand Down
14 changes: 0 additions & 14 deletions test/parallel/test-warn-multipleResolves.mjs

This file was deleted.