【JavaScript】例外処理とエラー

例外処理とエラー JavaScript

この記事では、JavaScript における例外処理について解説する。

例外処理とは

例外(exception)とは、「通常とは異なる」といった意味の言葉だが、JavaScript における例外も同様の意味を持っている。

例えば、JavaScript のコードを実行している JavaScript エンジンがそのコードに含まれている矛盾に遭遇したとき、それ以上は通常の処理を続けることができないため、例外が発生したことを通知してくれる仕組みが存在する。

下の画像は、user オブジェクトのニックネーム(nickname)を小文字に変換し、コンソールに表示させようとするコードを実行している。

例外の発生を JavaScript エンジンが通知してくれている

しかし、このコードにはバグが存在し、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汎用的なエラー
EvalErroreval 関数に関するエラー
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 {
    // それ以外の場合の処理
  }
}

タイトルとURLをコピーしました