プログラムの予期していないエラーを意図的に回避しておくことができる例外処理。
非同期処理においても例外処理ができるようになると、さらに効率良く安全性の高いプログラムを書いていくことができます。

そこで今回は、Promise・Async/Awaitを使った例外処理について解説していきます。

同期処理を使った例外処理

まずは、同期処理での例外処理についておさらいです。

try {
  throw new Error('例外が発生');
} catch(error) {
  console.log(error.message);
} finally {
  console.log('処理が完了');
}
console.log('外の処理');
// '例外が発生'
// '処理が完了'
// '外の処理'

try…catch文のtryブロックで例外が発生したため、投げられたErrorオブジェクトがcatchブロックに渡ります。
その後、必ず実行されるfinallyブロックに渡り、try…catch文の外の処理が実行されます。

このように、同期処理での例外処理は、trycatchfinallytry…catch外のように、上から順番に実行されていることが分かります。

Promiseを使った例外処理

次に、Promiseを使った例外処理について見ていきましょう。

catchメソッドを使用

Promiseでは、catchメソッドを使って例外処理を行っていきます。

const sample = () => {
  return new Promise((resolve, reject) => {
    reject(new Error('例外が発生'));
  });
};

sample()
  .catch((error) => console.log(error.message))
  .finally(() => console.log('処理が完了'));

console.log('外の処理');
// '外の処理'
// '例外が発生'
// '処理が完了'

非同期処理が行われている間に、外の処理が先に実行されていることが非同期処理の特徴です。

Promiseでは、reject状態のPromiseオブジェクトの処理をcatch内で行うことができるため、例外処理の際にtry…catch文を書く必要がありません。
つまり、エラーとなったオブジェクトをcatchメソッドで処理することができます。
また、rejectのnew Errorによって作成したErrorオブジェクトをcatchメソッドで受け取り、その中でスタックトレースを行うことができます。

上記のコードは、次のものと同様の動作をします。

const sample = () => {
  return new Promise((resolve, reject) => {
    throw new Error('例外が発生');
  });
};

sample()
  .catch((error) => console.log(error.message))
  .finally(() => console.log('処理が完了'));

console.log('外の処理');
//'外の処理'
//'例外が発生'
//'処理が完了'

throw new ErrorでErrorオブジェクトを投げ、catchメソッドで受け取るパターンです。
Promiseオブジェクトをrejectすることと同じ役割があります。

thenメソッドの動き

では、catchメソッドの前後にthenメソッドを記述するとどうなるでしょうか。

const sample = () => {
  return new Promise((resolve, reject) => {
    reject(new Error('例外が発生'));
  });
};

sample()
  .then(() => console.log('実行されない')) // 例外処理前のため実行されない
  .catch((error) => console.log(error.message))
  .finally(() => console.log('処理が完了'))
  .then(() => console.log('実行される')); // 例外処理後のため実行される

console.log('外の処理');
// '外の処理'
// '例外が発生'
// '処理が完了'
// '実行される'

例外が発生した場合、thenメソッドではなく、catchメソッドの処理が実行されるため、1つ目のthenメソッドではその中の処理は実行されません。
しかし、2つ目のthenメソッドは、既に例外処理が完了しているため有効となります。

Async/Awaitを使った例外処理

今度は、Async/Awaitでの例外処理を見ていきましょう。

try…catch文を使用

Async/Awaitでは、try…catch文を利用して例外処理を行うことができます。

const sample = async () => {
  throw new Error('例外が発生');
};

const getError = async () => {
  try {
    const result = await sample();
    console.log('実行されない');
  } catch(error) {
    console.log(error.message);
  } finally {
    console.log('処理が完了');
  }
}:

getError();
console.log('外の処理');
// '外の処理'
// '例外が発生'
// '処理が完了'

Async/AwaitもPromiseと同様に、非同期処理が行われている間に、外の処理が先に実行されています。

tryブロック内のawait sample()で例外が発生したため、そのブロック内の以降の処理は行われず、catchブロック内の処理に移り、最終的にfinallyブロックの処理が行われます。

catchメソッドを使用

もしtry…catch文がない場合は、asyncファンクションの呼び出しによって生成されたPromiseは拒否されます。
その場合、catchメソッドを使ってErrorオブジェクトを受け取ることもできます。

const sample = async () => {
  throw new Error('例外が発生');
};

const getError = async () => {
  const result = await sample();
  console.log('実行されない');
};

getError().catch((error) => console.log(error.message));
// '例外が発生'

上記のことから、try…catch文を使わなくてもcatchメソッドを使えば実質的に例外処理が行えるということになります。

try…catch文を使う方法でも、catchメソッドでエラーを拾う方法でも通常は問題はありません。
しかし、try…catch文を使うと、Errorオブジェクトが投げられた時に、より同期処理的に例外処理を行うことができます。
メソッドを繋がなくても例外処理を行えるため、Async/Awaitを使う際には、try…catch文での例外処理を知っておくと良いでしょう。

補足として、AwaitはAsyncファンクション内のみでしか使用できません。
そのため、asyncファンクション外にあるエラーを処理するために、catchメソッドやthenメソッドを追加することがあります。

まとめ

今回は、非同期処理における例外処理について解説しました。

これまでの例は、PromiseとAsync/Awaitをそれぞれ別々に書く方法でしたが、実際にはPromiseとAsync/Awaitの両方を使う場合もあります。
非同期処理の関数自体はPromiseで表現し、受け取ってから解決するまでをAsync/Awaitで表現するパターンなどです。

まずは、それぞれの特徴を理解してから、使いやすい方を選ぶことをおすすめします。

非同期処理と例外処理の関連記事

例外処理とエラーオブジェクト
Promise -then・catch
Async/Await