try{}内の処理は必ず実行され、そこで例外が投げられたらcatch{}が実行される。finally{}は任意だが、記述がある場合は最後に「必ず」実行される。

これは、一般に例外処理と呼ばれる構文である。

比喩で理解する例外処理

JavaScriptに限らず、プログラミングにおける例外は、そのコードが想定する条件が破られた状態を指す。そのコードで認められないもの=例外のようなイメージである。

try

例えば、私が伊勢エビのようなものを顔面にくくり付けて深夜に徘徊しているとする。一般常識に鑑みて、少し危険な匂いがするはずだ。地域の安全のためにも、第三者に危害を及ぼす可能性があるかどうかを判断する必要がある。

つまり、警察組織による職務質問が行われるべきだ。

このように、有害・無害を見極める処理がtry{}である。

catch

さて、警察組織によるトライ(職務質問)の結果、とんでもない事実が発覚する。よく見ると私が顔面にくくり付けているのは伊勢エビではなく、フェイスハガー(エイリアンの生殖器)だったのだ。これは明らかにヤバい。例外中の例外である。絶対に取り逃がさないよう、然るべき組織に対応をぶん投げる必要がある。

このぶん投げられた対応を受け止めるのがcatch{}である。

また、try{}からcatch{}へ処理を移行するための、応援要請にあたる処理にはthrow文を使う。一般には、文字通り「例外を投げる」などと表現される。

finally

警察組織が動く以上、結果に関わらず記録が求められる。この結果如何によらず最後に必ず実行される処理がfinally{}にあたる。

変な例えのせいで逆にわかりにくくなったかもしれないが、finallyは必要がなければ別に書かなくても良い。

サンプルコード

以下、上記の比喩をコードで表現したものである。

js
const me = { with: "フェイスハガー" };

try {
  syokushitsu(me);
  console.log("問題なし");
} catch {
  console.log("然るべき措置");
} finally {
  console.log("報告書を書く");
}

function syokushitsu(person) {
  if (person.with === "フェイスハガー") {
    throw "";
  }
}

/**
 * 結果:
 * '然るべき措置'
 * '報告書を書く'
 */

try{}catch{}finally{}も、特別難しいことはない。単純に、それぞれのブロックで実行したい処理を{}の中に書けばいい。

ただ、catch{}の中で例外の内容を知りたいケースが少なくない。上の例ではthrow文で空文字を投げているが、必要に応じて例外に関連する情報をcatchに送ることができる。

throw文

throw文は、関数のどこにでも書ける。

throw文が実行されると、その時点で関数の処理は停止し、一番近くの(コールスタック内の最初の)catch{}に処理が移動する。

catch{}が存在しない場合、コンソールにUncaught xxx(xxxは投げられた例外の内容)と表示して、プログラムを終了する。try{}に含めずにトップレベルにthrow文を書いても、同様の挙動となる。

throw文の書き方

throw文は、以下のように書く。なお、throwで投げた式は、catch{}の引数として受け取る。引数名は任意だが、errerrorとするのが一般的である。

js
try {
  throw;
} catch (任意の変数名) {
  // 任意の処理
}

JavaScriptで有効な式であれば、どのような値でも例外として投げることができる。

ただ、実際には、Errorオブジェクトを投げる慣習がある。

Errorオブジェクトは、Error('メッセージ')あるいはnew Error('メッセージ')のようにして生成する。

キャッチしたエラーオブジェクトをさらに上位のcatch{}に投げたい時がある。そういった場合は、causeプロパティを持つオブジェクトを第二引数に渡す。このcauseプロパティは、大元のエラーオブジェクトを保持することが期待される。

js
try {
  something();
} catch (err) {
  throw new Error("something()関数で例外が発生したっぽい", { cause: err });
}

Errorオブジェクトの種類

Error()は汎用的なエラーオブジェクトを生成するが、JavaScriptには特定のエラーに応じたエラーオブオブジェクトも用意されており、必要に合わせて使い分けることもできる。

種類 概要
EvalError グローバル関数eval()に関連して発生するエラーを表すインスタンスを作成。
RangeError 数値変数またはパラメーターが有効な範囲を外れたときに発生するエラーを表すインスタンスを作成。
ReferenceError 無効な参照を解除したときに発生するエラーを表すインスタンスを作成。
SyntaxError 構文エラーを表すインスタンスを作成。
TypeError 変数またはパラメーターが有効な型ではないときに発生するエラーを表すインスタンスを作成。
URIError encodeURI()またはdecodeURI()に無効なパラメーターが渡されたときに発生するエラーを表すインスタンスを作成。
AggregateError 複数のエラーを報告する必要がある場合(例: Promise.any()によって複数のエラーが報告される場合)に、複数のエラーを1つのエラーとしてまとめたインスタンスを作成。

例外処理の注意点

try...catch...finallyを使う際に注意したいポイントをまとめる。

tryはcatch、あるいはfinallyとセットで書く

try{}には、catch{}あるいはfinally{}とセットで使う必要がある。try{}単体で使うと構文エラーとなる。

また、catchfinallyも単体では使えず、いずれも構文エラーとなるので注意したい。

ちなみにtry{}finally{}のようにすると、tryfinallyが処理された後に上位のcatchに処理が移動する。

js
function test() {
  try {
    console.log("@try");
    throw "";
  } finally {
    console.log("@finally");
  }
}

try {
  test();
} catch {
  console.log("@catch");
}

/**
 * 結果:
 * @try
 * @finally
 * @catch
 */

try/catch/finallyのどれでも値を返せる

try{}catch{}finallyも、値を返すことができる。ただし、各ブロックのreturn文の有無によって返される値が違ってくるため、注意が必要である。

まず、finally{}が値を返す場合はその値が優先される。

js
function test(isNG) {
  try {
    if (isNG) {
      throw "";
    }
    return "@try";
  } catch {
    return "@catch";
  } finally {
    return "@finally";
  }
}

console.log(test(true));
// '@finally'
console.log(test(false));
// '@finally'

finally{}が値を返さない場合、例外が発生しなければtry{}、発生すればcatch{}から値が返される。

js
function test(isNG) {
  try {
    if (isNG) {
      throw "";
    }
    return "@try";
  } catch {
    return "@catch";
  } finally {
    // 値を返さない
  }
}

console.log(test(true));
// '@catch'
console.log(test(false));
// '@try'

returnfinally{}に書くと混乱の元になりかねないので注意したい。

参考資料