I want to await two promises that run in parallel. I do not want to await each promise serially (which works but is slower).
For that reason I thought I could create two promises first to get them rolling, say two network requests, then await them and be able to catch errors in a catch block. That assumption seems to be incorrect as I get a warning when running this example code.
async function testMultipleAwait() {
try {
const aPromise = new Promise((resolve) => {
setTimeout(() => resolve('a'), 200);
});
const bPromise = new Promise((_, reject) => {
setTimeout(() => reject('b'), 100);
});
const a = await aPromise;
const b = await bPromise;
} catch (e) {
console.log('Caught error', e);
}
}
testMultipleAwait();
Does NOT result in "Caught error" output, instead I get
tsc test-try-catch-await.ts && node test-try-catch-await.js
(node:31755) UnhandledPromiseRejectionWarning: b
(node:31755) UnhandledPromiseRejectionWarning: 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: 1)
(node:31755) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
Caught error b
(node:31755) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
I think I know what the issue is. Even though the promises kick-off in a concurrent fashion, you are still awaiting aPromise
and bPromise
sequentially:
const a = await aPromise; // First await this...
const b = await bPromise; // and then start to await this
This is not so much of a problem when both promises fulfill. It would keep jou waiting about as much time as
Promise.all
would, and then happily continue. That is why this problem is not so obvious at all...
It's important to know that under the hood this try-catch is converted to a promise, due to async/await. That means that whatever comes after the the statement awaited first will end up in a promise.then callback function.
So, const b = await bPromise
will not run before const a
has arrived (after 200ms). bPromise
fails 100ms sooner.
This is not to say that async/await does not pick up on the error or attach your catch
block (as promise.catch(...)) altogether. After all, there is terminal output of both the node warning and your catch handler:
tsc test-try-catch-await.ts && node test-try-catch-await.js
1 first node sees the error > (node:31755) UnhandledPromiseRejectionWarning: b
2 (node:31755) UnhandledPromiseRejectionWarning: 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: 1)
3 (node:31755) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
4 and then your catch handler > Caught error b
5 (node:31755) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
So de catch clause does work, but the async function does not bother to attach it to bPromise
until after at least 200ms. Line 5 seems to confirm this:
PromiseRejectionHandledWarning: Promise rejection was handled asynchronously.
Rejection errors get thrown as soon as the microtask queue is empty.
The promise rejection was handled, but node thinks you're too late. You can fix this issue by using Promise.all
. This way you await
once and your async function will catch every potential error first.
// Everything just as it is.. and then:
const [a, b] = await Promise.all([
aPromise,
bPromise,
]);
Because I was curious I entered your code in the chrome console to see what would happen. An error log pops up for a very short period (I'm guessing 100ms). Looking at this output you can just hear chrome saying:
"Ah wait! It is being caught after all. Here's the message!"
Click for gif animation.
A nice way to wait for several Promise
s to resolve to use the Promise.all
function. It expects an Array
of Promise
s, and produces a Promise
that resolves to an Array
containing the values that the individual Promise
s resolved to. Furthermore, it only resolves after the last Promise
resolves. If any of its input Promise
s rejects, then the entire Promise.all
expression rejects as well. It effectively "runs" all of its input processes "at the same time", emulating the classic "fork-join" pattern.
The reason you are getting that error is due to the fact that your timeout process is started as soon as you define those Promise
s, and not when you await
them, several lines later. You can see this if you log some text in the definition of one of the Promise
s, and then log something else before your await
expressions:
async function testMultipleAwait() {
try {
const aPromise = new Promise((resolve) => {
console.log('starting timeout');
setTimeout(() => resolve('a'), 200);
});
const bPromise = new Promise((_, reject) => {
setTimeout(() => reject('b'), 100);
});
console.log('awaiting');
const a = await aPromise;
const b = await bPromise;
} catch (e) {
console.log('Caught error', e);
}
}
testMultipleAwait();
// logs:
// "starting timeout"
// "awaiting"
To fix this immediate issue, you could convert those Promise
s to functions, and then call them, or await
them "immediately":
// Option 1
async function testMultipleAwait() {
try {
const aPromise = () => new Promise(resolve => {
setTimeout(() => resolve("a"), 200);
});
const bPromise = () => new Promise((_, reject) => {
setTimeout(() => reject("b"), 100);
});
const a = await aPromise();
const b = await bPromise();
} catch (e) {
console.log("Caught error", e);
}
}
testMultipleAwait();
// Option 2
async function testMultipleAwait() {
try {
await new Promise(resolve => {
setTimeout(() => resolve("a"), 200);
});
await new Promise((_, reject) => {
setTimeout(() => reject("b"), 100);
});
} catch (e) {
console.log("Caught error", e);
}
}
testMultipleAwait();
Bringing this all together, to have them run in parallel, you could try something like this:
async function testMultipleAwait() {
try {
await Promise.all([
new Promise(resolve => {
setTimeout(() => resolve("a"), 200);
}),
new Promise((_, reject) => {
setTimeout(() => reject("b"), 100);
})
]);
} catch (e) {
console.log("Caught error", e);
}
}
testMultipleAwait();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With