Awaiting two promises in try/catch block results in "Unhandled promise rejection" [duplicate] Awaiting two promises in try/catch block results in "Unhandled promise rejection" [duplicate] typescript typescript

Awaiting two promises in try/catch block results in "Unhandled promise rejection" [duplicate]


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.js1 first node sees the error     > (node:31755) UnhandledPromiseRejectionWarning: b2                                 (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 b5                                 (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.

chrome output after running the code


A nice way to wait for several Promises to resolve to use the Promise.all function. It expects an Array of Promises, and produces a Promise that resolves to an Array containing the values that the individual Promises resolved to. Furthermore, it only resolves after the last Promise resolves. If any of its input Promises 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 Promises, 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 Promises, 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 Promises to functions, and then call them, or await them "immediately":

// Option 1async 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 2async 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();


Let's see if I can explain this in an understandable manner, here we go ...

Upon creation of the bPromise, the callback of your setTimeout-call (i.e. the promise's rejection) is "pushed" into the timersqueue of the event loop. Now, when the event loop enters its next iteration, the promise queue does not hold the handling of await bPromise yet, but since the ~ 100 ms have already passed, the callback is executed and you get the Unhandled promise rejection error.

You need to immediately await the newly created promise, as the event loop's promise microtask-queue won't be empty this time and will now be executed before the callback in the timers-queue is processed, thus being able to actually catch the error:

const bPromise = await new Promise((_, reject) => {      setTimeout(() => reject('b'), 100);});

I recommend reading this and this to get a better grasp of how the event loop works.