HOWTO · JavaScript

JavaScript で catch を使わない try

今日の記事では、JavaScript で catch を実装せずに try ステートメントについて学習します。

このページの内容

はい。JavaScript の try ステートメントは、finally 句があれば catch を省略できます。このチュートリアルでは、catch なしの try が有効な場面、スローされたエラーがどうなるのか、そしてこの形式が適切な選択肢となるケースを説明します。

JavaScript で catch なしの try の動作

JavaScript の try ステートメントは、エラーをスローする可能性のあるコードを囲むもので、ステートメントを記述できる場所ならどこにでも置けます。スクリプトのトップレベル、関数の内部、あるいは別の try の内部にネストした形でも有効です。できないのは単独で置くことだけです。try ブロックの後には catch 句、finally 句、またはその両方が続く必要があるため、どちらも付かない裸の try は構文エラーになります。中括弧 {} は、ステートメントが 1 つだけの場合でも常に必須です。

構文:

このセクションのコードと表示されるラベルや出力は英語のままです。

try {
  // try_statements: code that may throw
} catch (exception_var) {
  // catch_statements: handle the thrown value
} finally {
  // finally_statements: always run afterward
}
  1. try_statements は実行対象のステートメントです。
  2. catch_statements は、try ブロック内で値がスローされたときに実行されるステートメントです。
  3. exception_var は、対応する catch ブロックでスローされた値を保持する省略可能な識別子です。
  4. finally_statements は、値がスローされたかキャッチされたかに関係なく、try ステートメントの完了後に実行されます。

なお、上のスケルトン内のコメントはコード例の一部のため英語のまま表示されます。

catch は finally がある場合にのみ省略可能で(その逆も同様)、文法上は次の 3 つの組み合わせだけが許可されます。

  1. try...catch
  2. try...finally
  3. try...catch...finally

catch ブロックがあり、try ブロック内のステートメント(またはそこから呼び出される関数)がスローすると、制御は直ちに catch に移ります。何もスローされなければ、catch は完全にスキップされます。finally ブロックは、値がスローされたかどうか、catch がそれを処理したかどうかに関係なく、try ブロックと catch ブロックが終了した後に必ず実行されます。

catch 句がなくても finally は先に実行され、その後スローされた値は呼び出し元へ伝播し続けます。ネストについても同じことが言えます。try ステートメントはネストでき、内側の try ステートメントに catch がない場合は、外側の try の catch ブロックが代わりにエラーを処理します。

完全な文法については、MDN の try...catch ドキュメントを参照してください。

例:catch なしの try...finally

次のスニペットは、それ自身の catch を持たない try...finally を外側の try...catch の中に配置しており、finally ブロックが実行される様子と、スローされた値がどこへ行くのかを確認できます。0 / 0 は NaN と評価されるため、throw new Error(0 / 0) はメッセージが NaN である Error をスローします。

この例を実行してください:

このセクションのコードと表示されるラベルや出力は英語のままです。

try {
  try {
    console.log('Executing try block')
    throw new Error(0 / 0)
  } finally {
    console.log('Final Call')
  }
} catch (error) {
  console.log('Outer catch received: ' + error.message)
}
Executing try block
Final Call
Outer catch received: NaN

内側の try はログ行を出力してからスローします。catch がないため、制御はそのまま finally に移り、コード中の文字列どおり Final Call と出力されます(ログに出る文字列はコード例と同じ英語のままです)。Error が伝播を続けるのは finally の完了後で、外側の catch がそれを受け取り、NaN というメッセージを出力します。外側の catch がなければ、同じエラーは呼び出し元に届くか、トップレベルでは Final Call の出力後にキャッチされないエラーとして表面化します。

catch なしの try を使う場面

ブロックに確実なクリーンアップが必要で、エラー自体は呼び出し元に任せたい場合に try...finally を使います。finally で行う典型的な処理には、リソースの解放、接続やファイルハンドルのクローズ、再有効化するボタンやリセットするフラグなど共有状態の復元、タイマーの停止などがあり、try ブロックが正常に終了してもスローしても必ず実行されなければならない作業です。

この形式は、コードがエラーに対して意味のある処理を追加できない場合に適しています。そのまま再スローするだけではノイズが増えるだけで、空の catch ブロックはあらゆる失敗を静かに飲み込んでしまい、バグが原因から遠く離れた場所で表面化することになります。try...finally ならクリーンアップを保証しつつ、呼び出し元がエラーを確認して処理できます。スローされた値を実際に調べたり、ログに記録したり、回復したりする必要がある場合は、代わりに本物の catch 句を追加してください。

catch なしの try の制限と落とし穴

  • finally はエラーを処理も抑制もしません。finally が終了した後、処理されなかったスロー値は呼び出し元へ伝播し続け、トップレベルではスクリプトを終了させるか、キャッチされないエラーとして報告されます。
  • スローされた値を観測できません。catch がなければ、この構文はスローされたものにアクセスできないため、メッセージをログに記録したりエラーの種類で分岐したりできません。
  • finally 内の制御フローは、保留中だったものをすべて上書きします。finally 内の return や throw は、伝播中の値(スローされたエラーを含み、それは静かに消えてしまいます)を破棄するため、finally は値を返さずスローもしない単純なクリーンアップに留めてください。
  • try...finally の後に続くステートメントは何もスローされなかった場合にのみ実行され、finally 自体も失敗パスで実行されるため、try ブロックが成功したことを前提にしてはいけません。