This article is a translation of a Japanese article I posted earlier.

Original article


For example, suppose we have the following function. This returns a promise that will either be resolved or rejected after 1 second. Which one it will be is randomly determined with a probability of 50%.

const mayReject = () => {
  return new Promise((resolve, reject) => {
    const shouldReject = Math.random() >= 0.5;
    const settle = shouldReject ? reject : resolve;
    const message = shouldReject ? 'rejected' : 'resolved';
    setTimeout(() => settle(message), 1000);
  });
};

Error handling of async await is generally done by try catch, so for example, when you call the above function, you can write the following.

If any of the three calls to mayReject() is rejected, it will go to catch at that point.

“try catch way”

(async () => {
  try {
    await mayReject();
    await mayReject();
    await mayReject();
  } catch (e) {
    console.error(e);
  }
})();

There is nothing wrong with the above, but it can also be written as follows. The result is exactly the same.

“catch way”

(async () => {
  await mayReject();
  await mayReject();
  await mayReject();
})().catch(console.error);

It’s a little cleaner now that the try catch block is gone.

If you want to handle errors one at a time, the “catch way” is much cleaner.

Assume that you want to handle each mayReject() error separately.

Here is an example of using try catch.

“try catch way”

(async () => {
  try {
    await mayReject();
  } catch (e) {
    console.error(e);
  }
  try {
    await mayReject();
  } catch (e) {
    console.error(e);
  }
  try {
    await mayReject();
  } catch (e) {
    console.error(e);
  }
})();

This code is so long.

On the other hand, if you use a simple catch, you can write something like this

“catch way”

(async () => {
  await mayReject().catch(console.error);
  await mayReject().catch(console.error);
  await mayReject().catch(console.error);
})();

This code is simpler than the previous one.

Another advantage of the “catch way”

A simple catch has another advantage.

In the case of try catch, if you want to use the result received from an asynchronous function in a try block outside the block, you need to do the following

“try catch way”

(async () => {
  let res1;
  let res2;
  let res3;

  try {
    res1 = await mayReject();
    res2 = await mayReject();
    res3 = await mayReject();
  } catch (e) {
    console.error(e);
  }

  console.info(res1);
  console.info(res2);
  console.info(res3);
})();

It is quite difficult to accept this code.

On the other hand, a simple catch can be written as usual, since it does not create a scope with try blocks.

“catch way”

(async () => {
  const res1 = await mayReject();
  const res2 = await mayReject();
  const res3 = await mayReject();
  console.info(res1);
  console.info(res2);
  console.info(res3);
})().catch(console.error);

Tip: The error handling process for both is slightly different.

The “try catch way” and the “catch way” work the same, but the process is slightly different.

As a specification of JavaScript, if a Promise waiting for await is resolved, it returns the result, but if it is rejected, it throws an error.

Therefore, the flow of error handling by the “Try Catch way” is as follows.

“try catch way”

(async () => {
  try {
    await mayReject(); // 1. Throw an error if rejected.
  } catch (e) {
    console.error(e); // 2. Catch a thrown error.
  }
})();

In this case, if the function being called by await is not enclosed in try catch, its parent function will return reject.

The parent function here is IIFE (async () => {})();.

Thus, the error handling flow of the “catch way” is as follows.

“catch way”

// 2. Return reject because an error was thrown internally.
(async () => {
  await mayReject(); // 1. Throw an error if rejected.
})().catch(console.error); // 3. Catch rejected.

Bonus: When using multiple catches

Finally, this is a bonus. If the following mayReject() is rejected, what will be printed to the console?

(async () => {
  try {
    await mayReject().catch(console.error('catched 1'));
  } catch {
    console.error('catched 2');
  }
})();
(async () => {
  await mayReject().catch(console.error('catched 1'));
})().catch(() => console.error('catched 2'));

The result is the same for both. Both will be printed.

catched 1
catched 2

So what about this one?

(async () => {
  try {
    await mayReject().catch(console.error('catched 1'));
  } catch {
    console.error('catched 2');
  }
})().catch(() => console.error('catched 3'));

The result is the same here. Note that catched 3 will not be printed.

catched 1
catched 2