カスタムエラー投げる — 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やプロパティで失わない。 - 投げるときは短く具体的に、扱うときは確実にログ・通知・分岐。
