【STEP.15】
JavaScriptの例外処理をマスターしよう!

例外処理とは

アプリを実行していると、「数値を受け取ることを想定した関数に文字列が渡された」「変数を参照しようとしたら未定義であった」などなど、開発時には想定しなかったさまざまなエラー(例外)が発生するものです。

もちろん、例外の種類によっては、プログラミング時に未然に回避できるものもありますが、「引数に予期せぬ値が渡された」「関数やクラスを意図せぬ方法で利用された」など、呼び出し側に基因する処理では、例外の発生を完全に防ぐことはできません。

そのような場合にもスクリプト全体が停止してしまわないようにするのが、例外処理の役割です。

try…catch…finally命令とは

例外処理を実現するのが、try…catch…finally命令です。

try…catch…finally命令
try {
  ...try_statements...
} catch(exception) {
  ...catch_statements...
} finally {
  ...finally_statements...
}
try_statements      例外を起こすかもしれない命令(群)
exception           例外情報を受け取るための変数
catch_statements    例外が発生した場合に実行する命令(群)
finally_statements  例外の有無に関わらず、最終的に実行される命令(群)

tryブロックで例外(エラー)が発生した場合、引数exceptionに例外情報が渡されたうえでcatchブロックが実行されます。

もちろん、例外が発生しなかった場合にはcatchブロックは実行されません。

finallyブロックは、例外の有無に関わらず、try、(実行された場合には)catchのあとで実行される後処理を表します。

例外処理

具体的なコードを見てみましょう。

let i = 1;
try {
  i = i * j; // 例外発生
} catch(e) {
  console.log(e.message);
} finally {
  console.log('処理が完了しました。');
}
j is not defined
処理が完了しました。

もしもtry…catch…finally命令を利用していない場合、「i = i * j; 」の時点で例外(未定義の変数を参照しようとしたため)が発生し、スクリプトが停止してしまうはずです。

しかし、tryブロックで例外が発生した場合には、処理はそのままcatchブロックに引き継がれ、後続の「console.log('処理が完了しました。');」もきちんと処理されていることが確認できるはずです。

なお、例外情報はcatchブロックにErrorオブジェクト(ここでは変数e)として引き渡されます。

ここでは、Errorオブジェクトに用意されているmessageプロパティを使って、エラーメッセージを表示しているだけですが、そのほかのブロック同様、必要に応じて任意の処理を記述することも可能です。

catch/finallyは省略可能

catchfinallyブロックはいずれかを省略可能で、以下のパターンで指定できます(tryだけ、という記述は意味もありませんし、構文的にも不可です)。

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

(1)は後処理がないパターン、(2)は処理すべき例外はないが、tryブロックでの処理の後始末だけは必要なパターンです。

たとえば以下は、パターン(2)の疑似コードです(以下のxxxxxFileは、ファイルを操作すること想定した仮想的な関数です)。

ファイルを読み書きしたら、finallyブロックでファイルを閉じることで、例外の有無に関わらず、確実にファイルを閉じることができます。

try {
  readFile(...);
  writeFile(...);
} finally {
  disposeFile(...);
}

throw命令とは

例外はプログラム中で発生したものを捕捉するばかりではなく、自分で発生させることもできます。

例外を発生させるのは、throw命令の役割です。

例外を発生させることを「例外をスローする」ともいいます。

throw命令
throw exp
exp     投げるエラー情報

たとえば以下は、円の面積を求めるgetCircleArea関数で、引数radius(半径)に数値以外、または負数を渡した場合に、例外を投げる例です。

function getCircleArea (radius) {
  if (typeof (radius) !== 'number' || radius <= 0) {
    throw new TypeError('引数radiusは正の数値でなければいけません。');	
  }
  return radius * radius * Math.PI;
}
try {
  console.log(getCircleArea(-3));
} catch (ex) {
  console.error(ex.message);
  // 結果:引数radiusは正の数値でなければいけません。
}

関数/メソッドで引数を受け取る場合には、最初に引数が意図した値であるかをチェックし、問題がある場合には例外を投げるのが基本です(throw new TypeError('引数radiusは正の数値でなければいけません。');)。

ここではTypeErrorを投げていますが、その内容に応じて次に掲げる表のようなXxxxxErrorオブジェクトも利用できます。

【主なXxxxxErrorオブジェクト】
オブジェクト概要
Error一般的なエラー
EvalErroreval関数に関連するエラー
RangeError値が許容範囲外、値が配列内に存在しない場合のエラー
ReferenceError未宣言の変数が参照された場合のエラー
SyntaxError文法エラー
TypeError値が期待した型でない場合のエラー
URIError不正なURIである場合のエラー

もちろん、適切なXxxxxErrorがない場合、ErrorXxxxxError)を継承して新たなErrorオブジェクトを定義してもかまいません(たとえばファイルに関連するエラーであれば、FileErrorのようなオブジェクトを定義することになるでしょう)。

MEMO
throwは型を問わない
そもそもthrow命令で指定する値は、XxxxxErrorオブジェクトでなくてもかまいません。

たとえば文字列や数値など、任意の型を指定できます。

ただし、エラーを型で明確に識別できるよう、まずはError、もしくはその派生オブジェクトを利用することをお勧めします。

スローした例外を補足しているのが、ソースコード9~11行目のcatchブロックです。XxxxxErrorオブジェクトからは、次に掲げる表のようなプロパティにアクセスできます。

【XxxxxErrorオブジェクトの主なプロパティ】
プロパティ概要
nameエラー名
messageエラーメッセージ

例外によって処理を振り分ける

例外をXxxxxErrorとして渡すようにすることで、例外の種類によって処理を振り分けることが可能になります。

具体的には、以下のようにinstanceof演算子を利用して、渡された例外の型を判別したうえで処理を行います。

} catch (ex) {
  if (ex.instanceof TypeError) {
    // 例外がTypeErrorの場合にだけ処理
  }
}

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です