JavaScript 逆引き集 | カスタムエラー投げる

JavaScript JavaScript
スポンサーリンク

カスタムエラー投げる — throw new Error('msg') の基本と実践

「想定外の入力」「外部APIの不整合」「ビジネスルール違反」など、続行できない状況を即座に知らせたいときは、エラーを投げて処理を止めます。メッセージだけでなく“エラーの種類”や“追加情報”を持たせると、原因の特定と復旧が早くなります。


基本の使い方と標準エラーの選び方

function divide(a, b) {
  if (typeof a !== "number" || typeof b !== "number") {
    throw new TypeError("引数は数値である必要があります");
  }
  if (b === 0) throw new RangeError("0で割ることはできません");
  return a / b;
}
JavaScript
  • 標準エラーの使い分け:
    • Error: もっとも一般的(種類不明の失敗)。
    • TypeError: 型やインターフェイスが不正。
    • RangeError: 範囲や上限が不正。
    • SyntaxError/ReferenceError: 構文や未定義参照(通常はJSランタイムが投げる)。
  • メッセージ: “何がダメか”が一目で分かる短い文にする。

カスタムエラークラスを作る(エラーに意味を持たせる)

class ValidationError extends Error {
  constructor(message, field, options) {
    super(message, options); // options?.cause を渡せる(ES2022)
    this.name = "ValidationError";
    this.field = field;      // 追加情報(どのフィールドが不正か)
  }
}

function createUser(input) {
  if (!input.name) {
    throw new ValidationError("nameは必須です", "name");
  }
  return { id: 1, ...input };
}
JavaScript
  • ポイント: extends Error で種類を明確化。name を設定し、必要なプロパティ(field, code, status など)を持たせる。
  • cause の活用: 下層のエラーを上位で包むと、原因チェーンが追いやすい。
class ExternalAPIError extends Error {
  constructor(message, { code, status, cause } = {}) {
    super(message, { cause });
    this.name = "ExternalAPIError";
    this.code = code;   // アプリ内コード
    this.status = status; // HTTPステータスなど
  }
}
JavaScript

実務で使う投げ方のパターン

  • ガード節(早期中断):
function process(order) {
  if (!order) throw new Error("orderがありません");
  if (!Array.isArray(order.items)) throw new TypeError("itemsは配列です");
  // 続行可能なら以降へ
}
JavaScript
  • 包んで再スロー(contextを足す):
async function loadProfile(id) {
  try {
    const res = await fetch(`/api/users/${id}`);
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return await res.json();
  } catch (err) {
    throw new ExternalAPIError("プロフィール取得に失敗しました", {
      status: err.status, cause: err
    });
  }
}
JavaScript
  • ビジネスルール違反を区別:
class BusinessRuleError extends Error {
  constructor(message, code) {
    super(message);
    this.name = "BusinessRuleError";
    this.code = code; // "NOT_ENOUGH_STOCK" など
  }
}
JavaScript

すぐ使えるテンプレート集

  • assert(条件NGなら投げる)
function assert(cond, msg = "Assertion failed") {
  if (!cond) throw new Error(msg);
}
// 使用例
assert(typeof x === "string", "xは文字列である必要があります");
JavaScript
  • validate(フィールド別にエラー)
function requireField(obj, key) {
  if (obj?.[key] == null) throw new ValidationError(`${key}は必須です`, key);
}
JavaScript
  • エラーファクトリ(統一メッセージ)
const Errors = {
  notFound: (entity, id) => new Error(`${entity}(${id})が見つかりません`),
  unauthorized: () => new Error("認証が必要です"),
};
throw Errors.notFound("User", 42);
JavaScript
  • causeでラップ
try {
  JSON.parse(badText);
} catch (err) {
  throw new Error("設定ファイルの読み込みに失敗", { cause: err });
}
JavaScript

よくある落とし穴と対策

  • catchで握りつぶす: 何事もなかったように進むと原因不明に。
    • 対策: ログを出す、ユーザー向け通知を行う、必要なら再スロー。
  • メッセージが抽象的: 「失敗しました」だけだと診断不能。
    • 対策: 具体的に「何が、どの値が、どう不正か」を含める。
  • 種類を使い分けない: すべて Error だと分岐しづらい。
    • 対策: TypeError/RangeError/カスタムエラーで区別。
  • 下層の原因を失う: ラップ時に元エラーを捨てると追跡困難。
    • 対策: cause や追加フィールドでコンテキストを保持。

練習問題(手を動かして覚える)

// 1) 型と範囲のチェックで適切な標準エラーを投げる
function toPercent(n) {
  if (typeof n !== "number") throw new TypeError("数値が必要");
  if (n < 0 || n > 1) throw new RangeError("0〜1の範囲で指定");
  return `${Math.round(n * 100)}%`;
}

// 2) ValidationErrorを作ってフィールド名を添える
class ValidationError extends Error {
  constructor(message, field) {
    super(message);
    this.name = "ValidationError";
    this.field = field;
  }
}
function saveUser(u) {
  if (!u?.email) throw new ValidationError("emailは必須", "email");
  return { ok: true };
}

// 3) 下層エラーをcauseで包んで再スロー
try {
  JSON.parse("{bad: json}");
} catch (e) {
  throw new Error("設定の読み込みに失敗", { cause: e });
}

// 4) エラー種別で分岐
try {
  toPercent("x");
} catch (e) {
  if (e instanceof TypeError) console.log("型を確認してください");
  else console.log("その他のエラー:", e.message);
}
JavaScript

直感的な指針

  • 「続行不能」なら迷わず投げる。種類を適切に選ぶ。
  • カスタムエラーで文脈(field/code/status)を持たせる。
  • 下層の原因は cause やプロパティで失わない。
  • 投げるときは短く具体的に、扱うときは確実にログ・通知・分岐。

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