[JavaScript講座] エラーと例外処理

当ページのリンクには広告が含まれています。
目次

エラーと例外のイメージ

「例外が投げられる」とは

「例外が発生する(throwされる)」=

  • その時点で 通常の処理フローを中断する
  • 一番近い try ブロックの catch を探しに行く
  • 見つからなければ、最終的には 実行環境(ブラウザ・Node)まで伝播し、エラーとして落ちる

という動きです。

function fn() {
  throw new Error("何かがおかしい");
  console.log("ここには来ない");
}

try {
  fn();
  console.log("ここにも来ない");
} catch (e) {
  console.log("catch:", e.message); // "何かがおかしい"
}
console.log("ここは実行される");

Error と組み込みエラー型

基本の Error オブジェクト

const err = new Error("何かエラーが起きました");

console.log(err.name);    // "Error"
console.log(err.message); // "何かエラーが起きました"
console.log(err.stack);   // スタックトレース(環境依存)
  • name:エラー種別名
  • message:説明文
  • stack:どこから来たか(デバッグ用)

throw するときは、

throw new Error("メッセージ");

が基本形です。

よく見る組み込みエラー型

1) TypeError

「型や使い方がおかしい」ときに出るエラー。

null.foo;           // TypeError: Cannot read properties of null ...
(123).toUpperCase(); // TypeError

自分で投げることもあります:

function double(x) {
  if (typeof x !== "number") {
    throw new TypeError("x must be number");
  }
  return x * 2;
}

2) ReferenceError

「参照している変数が存在しない」ときに出る。

console.log(foo); // ReferenceError: foo is not defined

3) SyntaxError

構文が間違っているとき。

eval("if ("); // SyntaxError
  • パース時に起きるので、普通のコード内で try/catch することはあまりない
  • eval / new Function で動的にコードを評価するときに出会うことがある

4) RangeError

「許容範囲を超えた値」のとき。

new Array(-1);  // RangeError
(123).toFixed(100); // RangeError: digits argument must be between ...

5) そのほか

  • URIErrordecodeURIComponent などで不正なエンコード
  • EvalError:歴史的な理由で残っている。実用で見ることはほぼない。
  • AggregateError:複数のエラーをまとめる(Promise.any などで使われる)

try / catch / finally の基本

基本構文

try {
  // 例外が起きるかもしれない処理
} catch (error) {
  // 起きたときの処理
} finally {
  // 成功しても失敗しても最後に必ず実行される処理(省略可)
}

catch は必須ではなく、try + finally という形も可能です。

try {
  // ...
} finally {
  // リソース解放など
}

シンプルな例

function parseJson(json) {
  try {
    const data = JSON.parse(json);
    console.log("解析成功:", data);
  } catch (e) {
    console.error("JSON parse error:", e.message);
  } finally {
    console.log("parseJson 完了");
  }
}

parseJson('{"valid": true}');
// 解析成功: { valid: true }
// parseJson 完了

parseJson('{invalid json}');
// JSON parse error: Unexpected token ...
// parseJson 完了
  • try 内で throw された例外だけが catch される
  • finally は「ログ出し」「接続を閉じる」「一時ファイル削除」などに使われる

catch の引数省略

try-catch だけの場合で、エラーオブジェクトを使わないなら、引数を省略できます(比較的新しめの仕様)。

try {
  riskyOperation();
} catch {
  console.log("何か失敗した");
}

throw による例外送出

何でも投げられる(推奨は Error

JavaScript では、理論上どんな値でも throw できます:

throw "error";   // 文字列
throw 123;       // 数値
throw { code: 1 }; // オブジェクト

が、実務・読みやすさの観点で非推奨 です。

おすすめは:

  • 基本は Error or そのサブクラス
  • throw new Error("...");
  • 種類によって TypeError, RangeError などを使い分け
function assertNumber(x) {
  if (typeof x !== "number") {
    throw new TypeError(`Expected number, got ${typeof x}`);
  }
}

throw の位置と「rethrow」

try {
  foo();
} catch (e) {
  if (e instanceof TypeError) {
    console.log("型エラー:", e.message);
  } else {
    // 想定外のエラーは再スローして上位に任せる
    throw e;
  }
}
  • 「ここで処理できるエラー」と「ここでは処理すべきでないエラー」を分けて扱うために
    一部だけハンドリングして、残りは throw し直す(rethrow) のがよくあるパターンです。

カスタムエラークラス

複雑なアプリやライブラリになると、

  • バリデーションエラー
  • 認証エラー
  • ネットワークエラー
  • ドメイン固有のエラー(ゲームロジック、業務ロジックなど)

を区別したくなります。
そのときに使うのが カスタムエラークラス です。

基本形

class ValidationError extends Error {
  constructor(message) {
    super(message);
    this.name = "ValidationError";
  }
}

function validateUser(user) {
  if (!user.name) {
    throw new ValidationError("name は必須です");
  }
}

try {
  validateUser({}); // name なし
} catch (e) {
  if (e instanceof ValidationError) {
    console.log("バリデーションエラー:", e.message);
  } else {
    throw e; // 想定外は再スロー
  }
}
  • Error を継承して name を自分でセットするのが定番
  • instanceof ValidationError で種類ごとに分岐できる

追加情報を持たせる

class HttpError extends Error {
  constructor(status, message) {
    super(message);
    this.name = "HttpError";
    this.status = status;
  }
}

throw new HttpError(404, "Not Found");
  • status のようなフィールドを付けておくと、エラーハンドラ側で HTTPレスポンスにそのまま反映できる

同期処理と非同期処理のエラーの違い

ここ、とても重要です。
try-catch は「そのスコープ内の同期処理」だけを捕捉します。

同期ならキャッチできる

try {
  JSON.parse("{invalid json}");
} catch (e) {
  console.log("同期エラーを捕捉した:", e.message);
}

これはOK。

setTimeout 内のエラーは外側の try では捕捉できない

try {
  setTimeout(() => {
    throw new Error("これは捕捉できない");
  }, 0);
} catch (e) {
  console.log("ここには来ない");
}

理由:

  • setTimeout のコールバックは 後で別タイミング で実行される
  • そのときには、外側の try はもう終わっている

setTimeout 内でエラーをキャッチしたければ、その中で try-catch する 必要があります。

setTimeout(() => {
  try {
    throw new Error("これはこの中で捕まえる");
  } catch (e) {
    console.log("timeout 内でキャッチ:", e.message);
  }
}, 0);

Promise のエラーは .catch or async/await + try-catch

非同期のエラーは「Promise の reject」として扱うのが基本です。

// Promiseチェーン
doAsync()
  .then(result => {
    // ...
  })
  .catch(err => {
    console.error("非同期エラー:", err);
  });

async/await ではこうなります:

async function main() {
  try {
    const result = await doAsync();
    // ...
  } catch (e) {
    console.error("async/await で非同期エラー捕捉:", e);
  }
}

ポイント

  • try / catch は「その await で拒否された Promise(reject)」を捕捉する
  • 逆に言うと、Promise を使わない非同期(生のコールバック) は、try だけでは救えない

グローバルの未処理エラー

ブラウザには:

  • window.onerror:キャッチされなかったエラー
  • window.onunhandledrejection:キャッチされなかった Promise の reject

Node.js には:

  • process.on("uncaughtException", ...)
  • process.on("unhandledRejection", ...)

といった「最終手段」もありますが、
基本は個別の箇所でちゃんとハンドリングする方針が大事です。

エラー処理の実務的な考え方

  • 「本当に例外にすべきか?」 を考える
    • 入力フォームのバリデーション失敗 → 例外ではなく「通常の結果」として返す方がよいことも多い
    • プログラミングミス(null参照など) → 例外で落として直すべき
  • エラーメッセージは「人間が読んで原因を推測できるレベル」で書く
  • 例外を握りつぶさない
    • 何もログを出さず catch (e) {} のように無視すると、デバッグ不可能になる
  • 「ここで回復できるエラー」と「できないエラー」を分けて、できないものは再スロー

<<前へ(バイナリデータと TypedArray)

>>次へ(ES Modules の基本)

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次