According to the Description section of async function
declaration in MDN:
async function foo() { const result1 = await new Promise((resolve) => setTimeout(() => resolve("1"))); const result2 = await new Promise((resolve) => setTimeout(() => resolve("2"))); } foo();
Note how the promise chain is not built-up in one go. Instead, the promise chain is constructed in stages as control is successively yielded from and returned to the async function. As a result, we must be mindful of error handling behavior when dealing with concurrent asynchronous operations.
For example, in the following code an unhandled promise rejection error will be thrown, even if a
.catch
handler has been configured further along the promise chain. This is becausep2
will not be "wired into" the promise chain until control returns fromp1
.async function foo() { const p1 = new Promise((resolve) => setTimeout(() => resolve("1"), 1000)); const p2 = new Promise((_, reject) => setTimeout(() => reject("2"), 500)); const results = [await p1, await p2]; // Do not do this! Use Promise.all or Promise.allSettled instead. } foo().catch(() => {}); // Attempt to swallow all errors...
I don’t understand why an unhandled promise rejection error will be thrown in the second foo
function. As far as I understand, await p1
unwraps p1
so evaluates to '1'
, then await p2
unwraps p2
so throws '2'
, then foo
catches '2'
, wraps '2'
into a rejected promise with the value '2'
and returns that promise, then catch(() => {})
is called on it so '2'
should be caught. But this is not the case, instead an unhandled promise rejection error is thrown:
Promise {
<pending>,
[Symbol(async_id_symbol)]: 2132,
[Symbol(trigger_async_id_symbol)]: 2126
}
> Uncaught '2'
> (node:32336) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 21)
I had to play with it myself to understand it.
The Problem is not await
ing a Promise
and just letting the exception bubble up, that is fine. The actual Problem with this code is the timeout. Or rather, the specific times the Promises resolve.
Notice that p2
rejects before p1
resolves:
const p1 = new Promise((resolve) => setTimeout(() => resolve("1"), 1000));
const p2 = new Promise((_, reject) => setTimeout(() => reject("2"), 500));
and that we await p1
before we await p2
:
const results = [
await p1,
await p2
];
This means, the moment p2
rejects we are not yet awaiting p2
, so there is no then
nor catch
handler registered for p2 yet. Thats why its unhandled.