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
}
try_statementsは実行対象のステートメントです。catch_statementsは、tryブロック内で値がスローされたときに実行されるステートメントです。exception_varは、対応するcatchブロックでスローされた値を保持する省略可能な識別子です。finally_statementsは、値がスローされたかキャッチされたかに関係なく、tryステートメントの完了後に実行されます。
なお、上のスケルトン内のコメントはコード例の一部のため英語のまま表示されます。
catch は finally がある場合にのみ省略可能で(その逆も同様)、文法上は次の 3 つの組み合わせだけが許可されます。
try...catchtry...finallytry...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ブロックが成功したことを前提にしてはいけません。