JavaScript/例外処理
例外処理(れいがいしょり、exception handling)とは、プログラムに異常が発生した場合に現在の処理を中断し、エラーメッセージを表示するなどの処理を行うことをいいます。
throw[編集]
throw文は例外を発生させます。
function reduce(array, callback, initialValue) {
if (typeof callback != 'function') {
throw new Error(callback + " is not a function");
}
// ...
}
reduce([], null); // "null is not a function" とエラー
throw文にはどんな値でも渡すことができますが、一般的にはError
などの例外オブジェクトを渡します。例外オブジェクトは生成されるときに例外発生時の状況を記録するため、デバッグが容易になるからです。特にReferenceErrorオブジェクトやSyntaxErrorオブジェクト、TypeErrorオブジェクトなどの例外オブジェクトは、エラーの種類(参照エラーや構文エラー、型エラーなど)を明示するのに用いられます。
function reduce( array, callback, initialValue ) {
if ( typeof callback != 'function' ) {
throw new TypeError( callback + " is not a function" );
}
// ...
}
reduce( [], null ); // "TypeError: null is not a function" とエラー
throw文で例外が投げられると、以降のプログラムの実行は中断され、処理系のエラーコンソールにエラーが表示されます。
try-catch[編集]
try文のブロックの中で例外が発生すると、catch節のブロックが実行され、例外が捕捉されます。try文のブロックで例外が発生しなかった場合は、catch節のブロックは実行されません。catch節のブロックが実行された後も、catch節のブロックの中で例外が発生しなければ、プログラムは中断せずに以降の処理を継続します。
try {
throw new Error( "エラー!" );
}
catch (e) {
console.log(e.message); // "エラー!" と表示
}
console.log("しかし処理は続行…");
catch節は複数置くことができます。また、catch節は必ずthrow文のパラメータeを受け取らなければなりません。eの変数名は任意の識別子。
finally[編集]
finally節は事後処理を行います。catch節の後にfinally節を書くと、例外が発生してもしなくてもfinally節が実行されます。
try {
console.log("try"); // 0. "try" と表示
}
catch (e) {
console.log("catch");
}
finally {
console.log("finally"); // 1. "finally" と表示
}
console.log("outside"); // 2. "outside" と表示
例外が発生した場合は、catch節が実行された後にfinally節が実行されます。finally節が実行された後は以降の処理を継続します。
try {
console.log("try"); // 0. "try" と表示
throw new Error();
}
catch (e) {
console.log("catch"); // 1. "catch" と表示
}
finally {
console.log("finally"); // 2. "finally" と表示
}
console.log("outside"); // 3. "outside" と表示
try文の後にはcatch節またはfinally節のいずれか、もしくは両方を置かなければなりません。catch節を除いたtry-finally節では、例外が発生してもしなくてもfinally節は実行されますが、catch節によって例外が捕捉されないので、例外が発生した場合は以降の処理を中断します。
return と finally[編集]
- return と finally
function div(n, d) { try { return n / d } catch (e) { console.log(e) } finally { console.log(`div(${n}, ${d}) -- finally`) } } console.log(`div(1, 2) = ${div(1, 2)}`) console.log(`div(1, 0) = ${div(1, 0)}`) console.log(`div(0, 0) = ${div(0, 0)}`)
- 実行結果
div(1, 2) -- finally div(1, 2) = 0.5 div(1, 0) -- finally div(1, 0) = Infinity div(0, 0) -- finally div(0, 0) = NaN
- try文にfinally節を持つ言語では「tryブロックで例外を出さずのreturn文に達したとき、finally節を実行するか?」が問題になります。
- ES/JSでは、return文に達してもfinally節が実行されます。
大域脱出[編集]
例外は大域脱出に使うこともできます。大域脱出とは、入れ子になった制御構造の内側から外側に制御を戻すことです。ラベルを伴わないbreak
やreturn
は最内側の制御構造(for
/while
/switch
と関数)を抜け出すだけですが、例外をthrow
すると文や関数を超えて制御が移ります。
この性質を利用すると二重以上のループや関数を脱出することができるのです。
しかし、大域脱出目的の例外の使用には慎重になってください。breakやreturnをラベルと共に使用することで、ほとんどの場合は例外を使うことなく大域脱出を達成できます。
- イテレーションメソッドからの脱出
- Array.prototype.forEach メソッドの様にcallbackの反復処理を行うイテレーションはbreakやreturnでは脱出ができないので、例外による大域脱出が適用なケースです。
- この場合も、for文に置換えるほうが可読性は向上するでしょう。
- 関数外のラベルにはbreakできません(動かない例)
const ary = new Array(10).fill(0).map((_, i) => i) LABEL: ary.forEach(function(x) { if (x > 5) break LABEL; // SyntaxError: Undefined label 'LABEL' console.log(x) })
- 例外を使ったイテレーションメソッドからの脱出
const ary = new Array(10).fill(0).map((_, i) => i); try { ary.forEach(function(x) { if (x > 5) { throw new Error(`x = ${x}`); } console.log(x) }); } catch {}
- 実行結果
0 1 2 3 4 5
- Array.prototype.every() を使ったイテレーションの中断
const ary = new Array(10).fill(0).map((_, i) => i); ary.every(function(x) { if (x > 5) { return false } console.log(x) return true });
- 実行結果
0 1 2 3 4 5