JavaScript | 例外とエラーハンドリング

JavaScript
スポンサーリンク

例外(エラー)って何?

プログラム実行中に「想定できない問題」が起きると、JavaScript は例外(Error オブジェクト)を投げます。例:存在しない変数にアクセスした、数値の扱いを間違えた、JSON が壊れている、など。例外を放置するとプログラムがそこで止まってしまうので、try…catch で受け止めて安全に処理します。


基本の書き方(復習)

try {
  // 問題が起きるかもしれない処理
} catch (e) {
  // 例外が投げられたときの処理(eは例外オブジェクト)
} finally {
  // (任意)例外が起きても起きなくても必ず実行される処理
}
JavaScript

例外の「種類」を使い分ける理由

エラーには種類(クラス)があり、代表的なものは:

  • TypeError:型が合っていない操作(例:undefined のプロパティを読む)
  • ReferenceError:存在しない変数を参照した
  • RangeError:数値の範囲が不正(toFixed の桁数が大きすぎる等)
  • SyntaxError:コード自体の構文が間違っている(evalJSON.parse で発生しやすい)
  • Error:上のどれにも当てはまらない一般的なエラー

種類ごとに処理を変えると「何が原因か」に応じた適切な対応(ログ出力、ユーザー向けメッセージ、リトライなど)ができます。

種類の判定には instanceof を使います:

if (e instanceof TypeError) { ... }
JavaScript

実例1 — toFixed の例(RangeError / TypeError が出る場面)

説明:Number.prototype.toFixed(digits) は数値を指定桁で丸めて文字列にするメソッド。引数 digits が大きすぎると RangeError、対象が数値でないと TypeError が起きることがある。

function formatNumber(value, digits) {
  try {
    return Number(value).toFixed(digits);
  } catch (e) {
    if (e instanceof RangeError) {
      console.error("桁数が大きすぎます:", e.message);
      return Number(value).toFixed(2); // フォールバック
    } else if (e instanceof TypeError) {
      console.error("数値ではありません:", e.message);
      return "—"; // 表示用の代替
    } else {
      console.error("予期せぬエラー:", e);
      throw e; // 再送出(呼び出し元に任せる)
    }
  } finally {
    // ここはログや計測コード置き場にできる
    // console.log("formatNumber 実行終了");
  }
}

console.log(formatNumber(1.2345, 2)); // "1.23"
console.log(formatNumber("abc", 2));  // エラー → "—"
console.log(formatNumber(1.23, 1000)); // RangeError → フォールバックで "1.23"
JavaScript

ポイント:

  • 特定の型のエラー(RangeError / TypeError)に応じてログや代替処理を行っている。
  • 想定外のエラーは throw e して上位に任せることもある(無理に握りつぶさない)。

実例2 — JSON.parse と SyntaxError

説明:JSON.parse に壊れた JSON を渡すと SyntaxError が出ます。外部データを扱うときは必ず try…catch で保護しましょう。

function safeParse(jsonStr) {
  try {
    return JSON.parse(jsonStr);
  } catch (e) {
    if (e instanceof SyntaxError) {
      console.warn("JSON の形式が不正です:", e.message);
      return null; // 失敗を示す
    } else {
      throw e;
    }
  }
}

console.log(safeParse('{"a":1}')); // { a: 1 }
console.log(safeParse('{a:1}'));   // null (警告ログ)
JavaScript

ポイント:

  • 外部ソースを直接 JSON.parse するのは危険 → 失敗に備える。

実例3 — undefined のプロパティ参照(TypeError)

説明:オブジェクトが undefined のときに .prop を読むと TypeError になります。アクセス前に存在チェックするのが基本ですが、どうしても一連の処理でまとめて捕まえたい場合は try…catch が役に立ちます。

function getNestedValue(obj) {
  try {
    // ここで obj が undefined だと TypeError になる
    return obj.child.value;
  } catch (e) {
    if (e instanceof TypeError) {
      console.error("オブジェクトが期待どおりではありません:", e.message);
      return null;
    } else {
      throw e;
    }
  }
}

console.log(getNestedValue({ child: { value: 42 } })); // 42
console.log(getNestedValue(undefined)); // null(ログ出力)
JavaScript

※ 実務では optional chaining(obj?.child?.value)を使う方が安全で簡潔です。


カスタムエラーの作り方(明確に区別したいとき)

自分でエラーの種類を作れば、より分かりやすくハンドリングできます。

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

function validateUser(user) {
  if (!user.name) throw new ValidationError("名前が必要です");
  if (typeof user.age !== "number") throw new ValidationError("年齢は数値である必要があります");
  return true;
}

try {
  validateUser({ age: "twelve" });
} catch (e) {
  if (e instanceof ValidationError) {
    console.warn("入力エラー:", e.message);
  } else {
    throw e;
  }
}
JavaScript

良い書き方・悪い書き方の例(初心者が陥りやすい落とし穴)

良い:特定のエラーに対して具体的な対処を行う

try {
  // 処理
} catch (e) {
  if (e instanceof SyntaxError) {
    // 再読み込み、ユーザーへ通知など
  } else {
    throw e;
  }
}
JavaScript

悪い:すべてを握りつぶして原因を隠す(デバッグ不能に)

try {
  // 処理
} catch (e) {
  // なにもログせずに無視 → NG
}
JavaScript

良い:ログを残して、ユーザーにはやさしいメッセージを出す

catch (e) {
  console.error(e); // 開発者向け
  showUserMessage("予期せぬエラーが発生しました。しばらく経ってからお試しください。");
}
JavaScript

練習問題(初心者向け)—— 解答・解説付き

問題1

toFixed を使って、入力値を指定桁で丸めて返す関数 safeToFixed(value, digits) を作りなさい。次の場合に備えて処理を分けること:

  • digits が負、または 100 を超える → RangeError として扱い、null を返す。
  • value が数値に変換できない → TypeError として扱い、null を返す。

解答例

function safeToFixed(value, digits) {
  try {
    // digits の範囲チェック(手動でチェックしてエラーを投げる)
    if (typeof digits !== "number" || digits < 0 || digits > 100) {
      throw new RangeError("digits は 0〜100 の整数でなければなりません");
    }
    const num = Number(value);
    if (Number.isNaN(num)) throw new TypeError("value は数値に変換できません");
    return num.toFixed(digits);
  } catch (e) {
    if (e instanceof RangeError || e instanceof TypeError) {
      console.warn(e.message);
      return null;
    }
    throw e;
  }
}
JavaScript

解説

  • toFixed 自体が RangeError を出す場面もあるが、ここでは事前にチェックして分かりやすいエラーメッセージを投げている。
  • Number.isNaN(num) で数値変換失敗を検出して TypeError を投げる。

問題2

外部から受け取った JSON 文字列をオブジェクトに変換する parseUser(jsonStr) を作る。次を満たすこと:

  • JSON が不正なら null を返す(SyntaxError を想定)。
  • JSON が正しくても name プロパティがなければ ValidationError(カスタム)として扱い、null を返す。

解答例

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

function parseUser(jsonStr) {
  try {
    const obj = JSON.parse(jsonStr);
    if (!obj.name) throw new ValidationError("name が必要です");
    return obj;
  } catch (e) {
    if (e instanceof SyntaxError) {
      console.warn("JSON の形式不正:", e.message);
      return null;
    } else if (e instanceof ValidationError) {
      console.warn("データ検証エラー:", e.message);
      return null;
    } else {
      throw e;
    }
  }
}
JavaScript

解説

  • JSON.parseSyntaxError と自分で投げた ValidationError を分けて処理。
  • どちらも呼び出し元に null を返して「失敗」を分かりやすくしている。

問題3(改善問題)

次のコードは何が良くない? 改善案を書きなさい。

try {
  doSomething(); // 何かの処理
} catch (e) {
  // ただ無視している
}
JavaScript

模範解答(改善点)

  • エラーを丸ごと無視していると、バグの原因が分からなくなる。最低でもログ出力すべき。
  • ユーザーにわかりやすいメッセージを表示するか、想定できるエラーだけを捕まえて処理する。
try {
  doSomething();
} catch (e) {
  console.error("doSomething でエラー:", e);
  showUserMessage("処理に失敗しました。ページを再読み込みしてください。");
  // 必要なら throw e; で再送出して上位で処理させる
}
JavaScript

実務でのベストプラクティス(初心者向けまとめ)

  1. すべてを握りつぶさない:原因追跡のために console.error などでログを残す。
  2. 種類ごとの処理instanceof やカスタムエラーで分ける。
  3. ユーザーにはやさしく、開発者には詳しく:ログは詳しく、ユーザー向けメッセージは簡潔に。
  4. 例外は仕様の一部として扱う:外部入力や非同期処理は例外が起きる前提で設計する。
  5. 非同期処理にも対応async/await の中でも try…catch を使う。例:
async function fetchData() {
  try {
    const res = await fetch("/api/data");
    const data = await res.json();
    return data;
  } catch (e) {
    console.error("取得失敗:", e);
    return null;
  }
}
JavaScript

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