First occurrence
The first time I saw this warning today:
1 | if(!Promise.first){ |
Console log with node --trace-warnings
flag:
1 | > (node:94838) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 3) |
If instead of catch
, I pass two callbacks to then
, the warning still shows like before:
1 | if(!Promise.first){ |
But if I run Promise.first
again, the warning does not appear any more.
Minimum reproduction
I reduced the reproduction to this:
1 | const k = Promise.reject(3) |
Or:
1 | const k = Promise.reject(3) |
1 | const k = Promise.reject(3) |
It is also notable that if I call k.then()
again afterwards, I get no warning; and if I just do const k = Promise.reject(3)
I don’t get the warning either.
By contrast, this does not fire the warning:
1 | Promise.reject(3).then() |
This does not fire the warning either:
1 | (() => { |
So there are two conditions to fire this warning:
- Promise defined on root level, i.e. assigned to a global object.
- No rejection handler is registered to it at the time of promise creation. The
processPromiseRejections
flags promise with no handlers synchronously so a statement right after the declaration still does not prevent the warning from happening.
Investigation
My search takes me to here:
https://github.com/nodejs/node/blob/main/lib/internal/process/promises.js#L166
When the Promise is defined on the root, is rejected but not handled (not passed a then
or catch
or finally
phrase), it is of type kPromiseRejectWithNoHandler
and gets added to a weak map maybeUnhandledPromises
with some info, and also pushed to array pendingUnhandledRejections
. processPromiseRejections
will synchronously mark warned
field of this promise to be true.
But later I chain to this promise a then
or catch
or finally
that attempts to handle the rejection, the promiseRejectHandler
will try to handle the rejection because the rejection type is now kPromiseHandlerAddedAfterReject
. handledRejection
gets called. handleRejection
finds out that this promise is still in maybeUnhandledPromises
with warned
set to true, therefore it fires a PromiseRejectionHandledWarning
: Promise rejection was handled asynchronously
This only warns once because once this rejection is handled, it gets removed from maybeUnhandledPromises
by handledRejection
:
1 | maybeUnhandledPromises.delete(promise); |
Back to the original warning
So if I do not put p into a variable and instead pass a Promise.reject(3)
to Promise.first
I don’t get the warning:
1 | if(!Promise.first){ |