この記事では、JavaScript における例外処理について解説する。
例外処理とは
例外(exception)とは、「通常とは異なる」といった意味の言葉だが、JavaScript における例外も同様の意味を持っている。
例えば、JavaScript のコードを実行している JavaScript エンジンがそのコードに含まれている矛盾に遭遇したとき、それ以上は通常の処理を続けることができないため、例外が発生したことを通知してくれる仕組みが存在する。
下の画像は、user オブジェクトのニックネーム(nickname)を小文字に変換し、コンソールに表示させようとするコードを実行している。
しかし、このコードにはバグが存在し、JavaScript エンジンは実行時に例外が発生したことを通知している。「Uncaught TypeError: Cannot read property ‘toLowerCase’ of undefined」
user オブジェクトには nickname プロパティが存在しないため、user.nickname の値は undefined となり、したがって user.nickname.toLowerCase() は undefined が持たない toLowerCase メソッドを呼ぶことになる。JavaScript エンジンはこれ以上の処理を進めることができなくなったため、例外の発生を通知している。
このコードの場合、例外の発生を回避するのは簡単だ。user オブジェクトに nikename プロパティを追加して、空文字列や “名無しさん” などを設定しておくのも一つの方法だろう。
しかし、user オブジェクトの元になるデータをネットワーク越しに、サーバーから取得する場合はどうだろうか。サーバーから取得したデータに nickname が存在しない場合は、空文字列や “名無しさん” を設定する、といった対処が必要になる。それでは name プロパティは大丈夫なのだろうか。サーバーはちゃんと name プロパティを含んだデータを返してくれるのだろうか、、、。
プログラマーが想定していない事態の発生を防ぐのはなかなか難しい。想定できていないのだから。
そこで JavaScrip エンジンが通知する例外の発生を捕えて、例外が発生したときにだけある処理を実行させたいというニーズが生まれる。
このような処理のことを例外処理(Exception handling)と呼び、例外処理を記述するための方法を与えてくれるのが try…catch…finally 構文だ。
try…catch…finally 構文
try…catch…finally 構文の各ブロックの意味は、次のコードのコメントの通り。
try {
// --- try ブロック ---
// try ブロックには通常の処理を記述する。
// 例外が発生すると、それ以降の処理は実行されずに catch ブロックに実行が移る。
} catch(e) {
// --- catch ブロック ----
// catch ブロックには例外が発生したときに実行させる処理を記述する。
// 変数 e には例外の内容が JavaScript エンジンによって格納されている。
// 変数 e の部分の名前は何でもよい。e や err がよく使われる。
} finally {
// --- finally ブロック ---
// finally ブロックには最後に必ず実行させたい処理を記述する。
// 例外が発生しなくても必ず実行される。
}
例えば、先ほどのコードを try ブロックの中に書いてみよう。
try {
const user = {id: 100, name: 'taro'};
const nickname = user.nickname.toLowerCase(); // <-- この行で例外が発生、catch に飛ぶ
console.log(nickname); // <-- この行は実行されない
} catch(err) {
console.log('例外が発生しました');
console.log('変数 err の内容:', err);
} finally {
console.log('finally block');
}
// 例外が発生しました
// 変数 err の内容: TypeError: Cannot read property 'toLowerCase' of undefined
// finally block
このコードを実行すると、コンソールには先ほどの JavaScript エンジンによる赤色のメッセージ「Uncaught TypeError: ~」は表示されなくなり、かわりに catch ブロックの記述によるコンソールへの出力が表示されるようになる。
try…cathc…finally 構文による例外処理を記述しない場合、例外が発生した時点で JavaScript エンジンは例外を通知して実行を停止してしまうが、例外処理を記述することで実行を停止することなく、その後の処理を継続できるようになる。
throw 文で例外を発生させる
throw 文
JavaScript エンジンが発生させる例外の他に、コードから意図的に例外を発生させる(例外を投げる)こともできる。それには throw 文を使用する。
throw の後ろに記述した値が catch ブロックの引数に渡される。
try {
console.log('-- try block start --');
throw 'throw exception';
console.log('--try block end--');
} catch(e) {
console.log('-- catch block --');
console.log(e);
}
// -- try block start --
// -- catch block --
// throw exception
throw するものはオブジェクトでも何でもよい
数値や文字列のようなプリミティブ型だけでなく、オブジェクトも throw できる。
try {
console.log('-- try block start --');
const obj = {name: 'error', message: 'throw object'};
throw obj;
console.log('--try block end--');
} catch(e) {
console.log('-- catch block --');
console.log(e);
}
// -- try block start --
// -- catch block --
// {name: "error", message: "throw object"}
Error オブジェクトを throw する
JavaScript にはエラーのための組み込みオブジェクト(コンストラクタ関数)が存在していて、主なものとして、次に示すものがある。どれも xxxxError という名前のコンストラクタ関数になっている。
オブジェクト名 | 意味 |
---|---|
Error | 汎用的なエラー |
EvalError | eval 関数に関するエラー |
RangeError | 許容範囲外の値を扱った場合のエラー |
ReferenceError | 無効な参照を行なった場合のエラー |
SyntaxError | 構文エラー |
TypeError | 期待される型でない場合のエラー |
URIError | 不正な URI である場合のエラー |
これらのコンストラクタから生成された、オブジェクトは次のようなプロパティを持っている。
プロパティ | 意味 |
---|---|
name | エラー名(コンストラクタ名) |
message | コンストラクタに渡した実引数 |
stack (非標準) | コールスタックの情報を含んだ文字列 |
ただし、JavaScript の仕様である ECMAScript で実装を要求しているのは name プロパティと message プロパティだけであり、最後の stack プロパティは ECMAScript には存在しない。しかし、多くのブラウザでサポートされている非標準のプロパティとなっている。ブラウザによってはさらに多くの非標準のプロパティをサポートしている。
例外を意図的に発生させて、例外処理として扱いたい場合には、これらを使って生成したエラーオブジェクトを throw することが一般的にはよく行われる。
try {
// ...
throw new Error('エラーが発生しました');
// ...
} catch(e) {
console.error(e);
}
// Error: エラーが発生しました
// at main.js:3
カスタムエラー
先ほど説明したエラーのための組み込みオブジェクトを継承して、独自のエラー(カスタムエラー)を作成して使用することもある。
カスタムエラーを作成することで、例外処理の中でどのようなエラーが発生したかによる条件分岐を細かく行えるようになる。
class MyError extends Error {
constructor(message) {
super(message);
this.name = 'MyError';
}
}
try {
// ...
throw new MyError('エラーが発生しました');
// ...
} catch(e) {
if (e instanceof MyError) {
// MyError の場合の処理
} else if (e instanceof TypeError) {
// TypeError の場合の処理
} else {
// それ以外の場合の処理
}
}