千里の道も1commitから

関西在住のWebエンジニアです。長らくブログをサボっていたので、1日1記事、1commitを目標にゆるりと頑張ります。積み重ねが研鑽となると信じて

JavaScriptのネストした関数のエラーハンドリングを理解する

JavaScriptではエラーが発生した時、強制的にプログラムが終了してしまいます。
具体的に何が起こるかと言うと後続の処理は実行されません。また、ユーザーはどのエラーが発生したかわからなくなります。Node.jsのサーバーではHTTPサーバーが止まる事象にもなってしまいます。

const parent = () => {
  child()
  console.log('後続処理');
}

parent();

以下のエラーが出て処理が強制終了します。

  child()
  ^
ReferenceError: child is not defined
    at parent (/Users/xxx/Desktop/MyProject/study/study-javascript/try-catch/none_try_catch.js:2:3)

適切に例外処理をすることで、エラーを補足し対処することが可能です。try .. catch 通常のエラーハンドリングはすぐにわかりますが、try ... catchはどの単位で書くべきでしょうか? ネストした関数は??

通常のエラーハンドリング

const parent = () => {
  try {
    throw new Error('call nomarl!!') // ここでスローした例外は
  } catch (ex) {  // ここでcatchされる
    console.log(ex.message);
  }
}

parent();

ネストした関数のエラーハンドリング

const parent = () => {
  try {
    child()
  } catch (ex) {  // ここでキャッチする
    console.log(`catch parent ${ex}`);
  }
}

const child = () => {
  // 何かの処理
  if (error) {
    throw new Error('call child!!')  // ここでスローした例外は
  }
}

parentTryCatch();

ネストした関数内であっても、親のtry ... catchで補足できることがわかります。

ネストした関数でエラーハンドリング + エラーハンドリング

const parent = () => {
  try {
    child()
  } catch (ex) {
    console.log(`cath parent ${ex}`);
  }
}

const child = () => {
  try {
    gChild()
  } catch (ex) { // ここでキャッチする。しかし、上へ伝播しないので、処理はここで終了する
    console.log(`cath child ${ex}`);
  }
}

const gChild = () => {
  ggChild() // ここで例外発生
  // throw new Error('call gChild')
}

parent();

上記だと、gChildでエラーが発生します。
親childで補足することはできますが、握り潰されてるので、parentに伝播することはありません。 ルーティング処理をしている場合等はトップレベル関数で補足したいでしょう。 その場合は、childでthrowして例外を伝播させると良いです。

良いエラーハンドリングとは

適切な箇所で、多すぎないエラーハンドリングが行われてると良いです。
エラーハンドリングができてなかったり、必要以上に try .. catchが書かれてるケースを見ます。

また、非同期のエラーハンドリングは async/awaitで囲む劇的に直感的になります。 JavaScriptのエラーついて知っておくべきこと - Qiita

参考

制御フローとエラー処理 - JavaScript | MDN JavaScriptのエラーついて知っておくべきこと - Qiita async関数においてtry/catchではなくawait/catchパターンを活用する - Qiita