throwを使った例外スローとエラーハンドリングの仕組みをわかりやすく解説

スポンサーリンク
スポンサーリンク
amazon
スマイルSALE
--:--:--
ad. 価格範囲を指定して商品を探せます

throwの概要

例外のスロー JavaScript予約語

throw

概要 throw文は、JavaScriptのプログラム実行中に、開発者が意図的に「エラー(例外)」を発生させて、通常のプログラムの処理の流れを強制的に中断させるための構文です。
throw が実行されると、そのエラーを捕まえてくれる try...catch ブロックにたどり着くまで、現在の処理をスキップしてジャンプします。

わかりやすく説明 throwはプログラムに対して「異常事態が発生したから、これ以上先の処理を進めずに、今すぐセーフティネット(catchブロック)へ緊急避難して!」とホイッスルを鳴らす役割を持っています。

  • JavaScriptの仕様上、文字列や数値など任意の値を出荷(スロー)できますが、エラー発生時の詳細な行数(スタックトレース)を記録するために、通常は Error オブジェクトを投げます。
  • throwによって投げられたエラーは、呼び出し元のどこかにある try...catch ブロックで適切に回収・処理する必要があります。
  • 万が一、誰にもキャッチ(捕捉)されなかったエラーは「未処理の例外」となり、最悪の場合プログラム全体の実行をその時点で強制停止させてしまいます。

throw文の基本的な使い方

JavaScriptが自動で起こすバグのエラーとは異なり、throw は「ユーザーがパスワードを入力し忘れた」「割り算の分母に0が渡された」など、プログラム的には動くけれど業務ルール上「これ以上進めてはダメな異常事態」を検知したときに自発的に使用します。
以下は、不正な計算を検知して throw で処理を中断させ、それを安全にキャッチする基本的な例です。

// 割り算を行う関数
function divide(a, b) {
    // 分母が0だった場合、数学的に不可能なため処理を中断させたい
    if (b === 0) {
        // throw文で新しいエラーオブジェクトを作成して投げます
        throw new Error("ゼロで割ることはできません。");
    }
    // エラーが起きた場合は上の行で処理が飛ぶため、この下の行は実行されません
    return a / b;
}

try {
    console.log(divide(10, 2)); // 出力: 5(正常な処理)
    console.log(divide(10, 0)); // ❗ ここでエラーが発生し、即座にcatchブロックへワープします
    console.log("この行は実行されません");
} catch (error) {
    // throwされたErrorオブジェクトが引数(error)に格納されます
    console.error("例外をキャッチしました:", error.message); // 出力: ゼロで割ることはできません。
}
  • if 文などの条件分岐と throw を組み合わせることで、想定外の不正なデータがシステムの奥深くに入り込むのを水際でガードできます。
  • try ブロックの内部で throw が実行された瞬間に、それ以降のコードはすべて無視され、即座に catch ブロックの安全なエラーハンドリングへ処理が移ります。

throw文の応用例

実際のWebアプリケーション開発では、単なる Error オブジェクトを投げるだけでなく、独自のカスタムエラークラス(例:入力チェック専用のエラーなど)を作って throw することもあります。
これにより、catch 側で「何が原因のエラーなのか」を instanceof を使って正確に見極め、表示する警告メッセージをきれいに分岐できるようになります。

// 入力チェック(バリデーション)専用のカスタムエラークラスを作成
class ValidationError extends Error {
    constructor(message) {
        super(message); // 親クラス(Error)のコンストラクタを呼び出します
        this.name = "ValidationError"; // エラーの名前を上書き
    }
}

function validateUser(user) {
    if (!user.name) {
        throw new ValidationError("名前が入力されていません。");
    }
    if (user.age < 18) {
        throw new ValidationError("18歳未満のユーザーは登録できません。");
    }
    console.log("ユーザー検証OK:", user.name);
}

try {
    validateUser({ name: "John", age: 17 }); // ❗ 年齢制限でthrowされます
} catch (error) {
    // 発生したエラーが「ValidationError」の仲間かどうかを判定します
    if (error instanceof ValidationError) {
        console.error("【入力チェックエラー】", error.message);
    } else {
        console.error("【予期せぬシステムエラー】", error);
    }
}
  • 標準のエラーと自作のエラーを明確に区別することで、「ユーザーのミスだから画面に優しい案内を出す」「システムの深刻なバグだから管理者に通報する」といった柔軟な設計が可能になります。
  • instanceof を用いた種類別のエラーハンドリングと組み合わせることで、堅牢なアプリケーションの基盤を構築できます。

注意点

  • 適切なエラーオブジェクトの使用: throw "エラー発生" のように、ただの文字列や数値をそのまま投げることも文法的には可能です。
    しかし、文字列のままだとエラーがコードの何行目で発生したのかという追跡情報(スタックトレース)が一切付与されないため、原因究明が非常に困難になります。必ず new Error() を使用して投げましょう。

    // ❌ NGな例:ただの文字列をスローすると、デバッグが難しくなります
    throw "入力エラー"; 
    
    // ⭕ OKな例:Errorオブジェクトを生成して投げるのが世界の共通ルールです
    throw new Error("入力エラーが発生しました");
    
  • 例外処理の範囲: 自分が作成した関数の中でどれだけ激しく throw を実行しても、それを呼び出す外側のコードのどこにも try...catch の防護壁が用意されていない場合、ブラウザの画面が固まったり、アプリ全体がクラッシュして停止してしまいます。
  • 過剰な例外処理を避ける: throw は「通常のプログラムの実行を強制終了させてワープする」という、非常に強力でコンピュータに負荷のかかる特殊な処理です。
    「ボタンのオン・オフ」や「単なる数値の大小比較」といった、通常の if / else 文の分岐で簡単に制御できるような一般的な処理の流れの中で、むやみやたらに throw を乱発するのはコードの可読性を下げるため避けましょう。

よくある質問

Q: throw文でスローできるデータには具体的にどのような制限がありますか?
A: 技術的な制限は一切ありません。
文字列、数値、真偽値(true/false)、普通のオブジェクト、配列、さらには関数さえも throw で投げることが可能です。
しかし、前述の通りJavaScriptの開発現場においては、特別な理由がない限りは Error オブジェクト、もしくはそれを継承して作ったカスタムエラーオブジェクトを投げるのが鉄則となっています。
Q: throwreturnの決定的な違いは何ですか?
A: どちらも「その場所で処理を終わらせる」という点では似ていますが、目的と終わらせ方が全く異なります。
return は、関数が予定通りの仕事をやり遂げて、呼び出し元に「はい、これが結果のデータだよ」ときれいにバトンを返す正常終了のためのコマンドです。
一方で throw は、「これ以上仕事は続けられない!」と仕事を放棄して、エラーを解決できる場所まで強制的にワープする異常終了(緊急事態)のためのコマンドです。

// return と throw のイメージの違い
function checkValue(val) {
    if (val === "OK") return "手続き完了"; // ⭕ 正常に結果を納品
    throw new Error("手続き不備");       // 🛑 異常事態として強制ストップ
}
Q: キャッチしたエラーを、ログだけ記録してさらに上の処理へそのまま横流しすることはできますか?
A: はい、可能です。
catch ブロックの中で受け取ったエラーオブジェクトを、もう一度 throw error; と記述することで、一度捕まえたエラーをさらに外側の try...catch へ向けて「再スロー(再投げ)」することができます。
「この関数内ではエラーログの保存だけを行い、実際の画面へのエラー表示は親玉の関数に任せたい」というような役割分担の際によく使われます。

// エラーの再スローの例
try {
    try {
        throw new Error("通信失敗");
    } catch (innerError) {
        console.warn("(子)ログに一時記録しました");
        throw innerError; // ❗ 捕まえたエラーをそのまま外へ投げ直します
    }
} catch (outerError) {
    console.error("(親)最終的にユーザーへエラーを表示します:", outerError.message);
}

まとめ

throw文は、予期しない不正な操作や致命的なデータ崩壊からアプリケーションを守るために、自ら警告のホイッスルを鳴らす強力な構文です。

  • try...catch のセーフティネットと正しく組み合わせることで、予期せぬエラーが起きてもアプリをクラッシュさせずに安全に復旧させることができます。
  • 文字列をそのまま投げるのではなく、デバッグに必要な情報がぎっしり詰まった Error オブジェクト(またはそのカスタムクラス)を投げるのが基本の綺麗なルールです。
  • return による正常な処理の終了と、throw による緊急事態の処理中断の性質を正しく理解し、システムの信頼性と安全性を最大化させていきましょう。