プログラムの予期していないエラーを意図的に回避しておくことができる例外処理。
当ブログでもJavaScriptにおける例外処理やエラーオブジェクトについて取り上げてきました。

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

そこで今回は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文の外の処理が実行されます。

このように、同期処理での例外処理は、tryブロック→catchブロック→finallyブロック→try…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で表現し、受け取ってから解決するまでをAsync/Awaitで表現するパターンなどです。

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

非同期処理やErrorオブジェクトの基本をおさえておくと理解しやすいため、以下を参考にしてみてください。

JavaScriptの関連記事