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
11 changes: 11 additions & 0 deletions doc/api/util.md
Original file line number Diff line number Diff line change
Expand Up @@ -1691,6 +1691,17 @@ console.log(util.isDeepStrictEqual(foo, bar, true));
See [`assert.deepStrictEqual()`][] for more information about deep strict
equality.

## `util.markPromiseAsHandled(promise)`

<!-- YAML
added: REPLACEME
-->

* `promise` {promise} The promise to mark as handled

Marks a promise as handled so that unhandled rejections are ignored and are not
reported to the `'unhandledrejection'` event.

## Class: `util.MIMEType`

<!-- YAML
Expand Down
8 changes: 8 additions & 0 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,13 @@ function deprecate(fn, msg, code, { modifyPrototype } = kEmptyObject) {
return internalDeprecate(fn, msg, code, undefined, modifyPrototype);
}

function markPromiseAsHandled(promise) {
if (!types.isPromise(promise)) {
throw new ERR_INVALID_ARG_TYPE('promise', 'Promise', promise);
}
binding.markPromiseAsHandled(promise);
}

// Keep the `exports =` so that various functions can still be monkeypatched
module.exports = {
_errnoException,
Expand Down Expand Up @@ -618,6 +625,7 @@ module.exports = {
}
return internalDeepEqual(a, b, skipPrototype);
},
markPromiseAsHandled,
promisify,
stripVTControlCharacters,
toUSVString(input) {
Expand Down
23 changes: 23 additions & 0 deletions src/node_util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,18 @@ using v8::CFunction;
using v8::Context;
using v8::DictionaryTemplate;
using v8::External;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::IndexFilter;
using v8::Integer;
using v8::Isolate;
using v8::KeyCollectionMode;
using v8::kPromiseHandlerAddedAfterReject;
using v8::Local;
using v8::LocalVector;
using v8::MaybeLocal;
using v8::Name;
using v8::Number;
using v8::Object;
using v8::ObjectTemplate;
using v8::ONLY_CONFIGURABLE;
Expand Down Expand Up @@ -467,6 +470,26 @@ void MarkPromiseAsHandled(const FunctionCallbackInfo<Value>& args) {
Local<Promise> promise = args[0].As<Promise>();
promise->MarkAsHandled();
promise->MarkAsSilent();

// If the promise is already rejected, then it may have already been
// reported to the unhandled rejection handler. Marking it as handled
// above does not trigger the v8 callback that updates it's status.
// So to avoid the notification we call out manually.
if (promise->State() == v8::Promise::kRejected) {
Environment* env = Environment::GetCurrent(args);
Local<Function> callback = env->promise_reject_callback();
CHECK(!callback.IsEmpty());

Local<Value> type =
Number::New(env->isolate(), kPromiseHandlerAddedAfterReject);
Local<Value> vargs[] = {type, promise, Undefined(env->isolate())};

USE(callback->Call(
env->context(), Undefined(env->isolate()), arraysize(vargs), vargs));

// Note that if callback->Call throws here, we go ahead and let that
// propagate.
}
}

void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
Expand Down
25 changes: 25 additions & 0 deletions test/parallel/test-mark-promise-handled.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict';

const common = require('../common');
const assert = require('node:assert');
const { markPromiseAsHandled } = require('node:util');

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

markPromiseAsHandled(Promise.reject(123));

{
const { promise, reject } = Promise.withResolvers();
markPromiseAsHandled(promise);
reject(123);
}

{
const { promise, reject } = Promise.withResolvers();
reject(123);
markPromiseAsHandled(promise);
}

assert.throws(() => markPromiseAsHandled(123), {
code: 'ERR_INVALID_ARG_TYPE',
});
Loading