読み込み中...
読み込み中...
読み込み中...
読み込み中...
読み込み中...
JavaScriptの Error オブジェクトは、実行時エラーの情報をカプセル化するための標準的なオブジェクトです。Error にはいくつかの組み込みサブクラスがあり、エラーの種類を分類できます。
| エラー型 | 発生する場面 | 例 |
|---|---|---|
TypeError | 型が不正な操作 | null.prop、undefined() |
ReferenceError | 未定義の変数を参照 | 宣言前の変数へのアクセス |
SyntaxError | 構文エラー | JSON.parse("{invalid}") |
RangeError | 範囲外の値 | new Array(-1) |
URIError | URI関数のエラー | decodeURIComponent("%") |
EvalError | eval関連(現在は非推奨) | ほぼ発生しない |
Error オブジェクトは message(エラーメッセージ)、name(エラー名)、stack(スタックトレース)の3つの主要プロパティを持ちます。スタックトレースはエラーが発生するまでの関数呼び出しの履歴で、デバッグに不可欠な情報です。
Error オブジェクトと instanceof による分岐
// Error オブジェクトのプロパティ
try {
const obj = null;
obj.property; // TypeError
} catch (err) {
console.log(err.name); // "TypeError"
console.log(err.message); // "Cannot read properties of null"
console.log(err.stack); // スタックトレース(行番号付き)
}
// エラーの種類による分岐
function handleError(err) {
if (err instanceof TypeError) {
console.log("型エラー: " + err.message);
} else if (err instanceof RangeError) {
console.log("範囲エラー: " + err.message);
} else if (err instanceof SyntaxError) {
console.log("構文エラー: " + err.message);
} else {
console.log("不明なエラー: " + err.message);
}
}
try {
JSON.parse("invalid json");
} catch (err) {
handleError(err); // "構文エラー: ..."
}チャレンジ
関数 safeParse を実装してください。JSON文字列を受け取り、パース成功時は { success: true, data } を、失敗時は { success: false, error } を返します。try/catch を使ってください。
ポイント
Error オブジェクトは message、name、stack の3プロパティを持ちます。instanceof でエラーの種類を判別でき、TypeError、RangeError、SyntaxError などの組み込みエラー型があります。
前提知識
この章の内容を理解するには、JavaScript基礎コースのエラーハンドリング・デバッグで学ぶ try/catch/finally の基本構文と Error オブジェクトの基本が前提となります。
アプリケーション固有のエラーを表現するために、Error を継承したカスタムエラークラスを作成できます。カスタムエラーを使うことで、エラーの種類をコードで明確に区別でき、エラーハンドリングのロジックが整理されます。
カスタムエラークラスを作る際のポイントは以下の通りです。
| ポイント | 説明 |
|---|---|
Error を継承する | extends Error で組み込みエラーの機能を引き継ぐ |
name プロパティを設定 | クラス名を設定してスタックトレースを見やすくする |
| 追加情報を持たせる | エラーコード、HTTP ステータス、詳細情報など |
super(message) を呼ぶ | 親クラスの constructor でメッセージを設定 |
カスタムエラークラスの階層構造
// カスタムエラークラスの基本
class AppError extends Error {
constructor(message, code) {
super(message);
this.name = "AppError";
this.code = code;
}
}
class ValidationError extends AppError {
constructor(field, message) {
super(message, "VALIDATION_ERROR");
this.name = "ValidationError";
this.field = field;
}
}
class NotFoundError extends AppError {
constructor(resource, id) {
super(`${resource}(ID: ${id})が見つかりません`, "NOT_FOUND");
this.name = "NotFoundError";
this.resource = resource;
this.id = id;
}
}
// 使用例
function findUser(id) {
const users = { 1: "田中", 2: "佐藤" };
if (!users[id]) {
throw new NotFoundError("ユーザー", id);
}
return users[id];
}
try {
findUser(99);
} catch (err) {
if (err instanceof NotFoundError) {
console.log(`${err.name}: ${err.message}`);
console.log(`コード: ${err.code}`);
}
}チャレンジ
ValidationError カスタムエラークラスを実装してください。コンストラクタは field(フィールド名)と message を受け取ります。name は "ValidationError"、field プロパティにフィールド名を保持してください。
ポイント
カスタムエラークラスは Error を継承して作成し、name プロパティとアプリケーション固有の情報(エラーコード、フィールド名など)を持たせます。instanceof で種類を判別し、適切なエラーハンドリングを実現できます。
非同期処理のエラーハンドリングは、同期処理とは異なる注意点があります。特に、キャッチされないPromiseの拒否(Unhandled Rejection)はアプリケーションの予期しない動作やクラッシュの原因になります。
| パターン | エラーの捕捉方法 |
|---|---|
| Promise チェーン | .catch() メソッド |
| async/await | try/catch 文 |
| グローバル | unhandledrejection イベント |
重要: async 関数内で await なしに Promise を返すと、その Promise のエラーは try/catch では捕捉できません。全ての Promise に対してエラーハンドリングを行うことが重要です。
Node.js では process.on('unhandledRejection', handler) で、ブラウザでは window.addEventListener('unhandledrejection', handler) でグローバルに未処理の拒否をキャッチできます。これは最後の砦であり、個別のエラーハンドリングが常に優先されるべきです。
非同期処理のエラーハンドリングパターン
// Promise チェーンのエラーハンドリング
fetch("/api/data")
.then(res => {
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
return res.json();
})
.then(data => console.log(data))
.catch(err => console.error("取得失敗:", err.message));
// async/await のエラーハンドリング
async function fetchData() {
try {
const res = await fetch("/api/data");
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
const data = await res.json();
return data;
} catch (err) {
console.error("エラー:", err.message);
return null; // デフォルト値を返す
}
}
// 複数の非同期処理のエラーハンドリング
async function fetchMultiple(urls) {
const results = await Promise.allSettled(
urls.map(url => fetch(url).then(r => r.json()))
);
const successes = results.filter(r => r.status === "fulfilled");
const failures = results.filter(r => r.status === "rejected");
if (failures.length > 0) {
console.warn(`${failures.length}件のリクエストが失敗しました`);
}
return successes.map(r => r.value);
}チャレンジ
async 関数 safeFetch を実装してください。URL を受け取り、成功時は { ok: true, data } を、失敗時は { ok: false, error } を返します。内部では fetchData(Promiseを返す模擬関数)を使ってください。
ポイント
非同期処理のエラーは .catch() または try/catch(async/await)で捕捉します。未処理の拒否は unhandledrejection イベントで最終防衛しますが、個別のハンドリングを優先しましょう。Promise.allSettled は複数の非同期処理のエラーを個別に処理するのに適しています。
エラーハンドリングの設計は、アプリケーションの信頼性と保守性に直結します。ここでは実践的なベストプラクティスを紹介します。
アプリケーションの各レイヤーでエラーを適切にキャッチし、上位に伝播させるか、その場で処理するかを判断します。
| レイヤー | エラーの扱い |
|---|---|
| ドメインロジック | ビジネスルール違反のエラーを throw |
| サービスレイヤー | 外部API・DB のエラーをキャッチして変換 |
| コントローラ/ハンドラ | エラーをユーザー向けメッセージに変換 |
| グローバル | 未捕捉エラーのログ記録と安全な表示 |
catch ブロックで何もしない(空の catch)は最も避けるべきパターンです。少なくともログ出力を行い、必要に応じて再 throw するか、エラー情報を上位に伝えましょう。
エラーの適切な処理と再throw
// NG: エラーを握りつぶす
try {
JSON.parse(input);
} catch (e) {
// 何もしない → バグの原因が分からなくなる
}
// OK: ログを記録してデフォルト値を返す
function parseConfig(input) {
try {
return JSON.parse(input);
} catch (err) {
console.warn("設定ファイルのパースに失敗:", err.message);
return {}; // 安全なデフォルト値
}
}
// OK: エラーを変換して再throw
async function fetchUserData(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 AppError(
"ユーザーデータの取得に失敗しました",
"FETCH_USER_FAILED"
);
}
}内部エラーの詳細(スタックトレース、SQL文、内部パスなど)をユーザーに見せてはいけません。セキュリティリスクにもなります。
| 層 | メッセージ例 |
|---|---|
| ユーザー向け | 「データの保存に失敗しました。もう一度お試しください。」 |
| ログ(開発者向け) | [ERROR] INSERT failed: duplicate key 'user_email_unique' |
例外を throw する代わりに、成功と失敗を明示的な型で返すResult パターンも有効です。
Result型パターンによる明示的なエラー処理
// Result 型パターン
function ok(value) {
return { ok: true, value };
}
function err(error) {
return { ok: false, error };
}
function divide(a, b) {
if (b === 0) {
return err("ゼロで割ることはできません");
}
return ok(a / b);
}
// 使用側: try/catch なしでエラーを処理
const result = divide(10, 0);
if (result.ok) {
console.log("結果:", result.value);
} else {
console.log("エラー:", result.error);
}
// "エラー: ゼロで割ることはできません"チャレンジ
Result パターンを使って safeDivide 関数を実装してください。ゼロ除算の場合は { ok: false, error: 'Division by zero' } を返し、正常な場合は { ok: true, value: 結果 } を返してください。
ポイント
エラーを握りつぶさず、適切にログ出力・再throw・変換を行いましょう。各レイヤーでエラー境界を設け、ユーザー向けメッセージと開発者向けログを分離します。Result パターンは例外の代替として明示的なエラー処理を実現します。
実務での活用
基礎で学んだ try/catch に加え、エラーの種類に応じて適切に処理を分岐させるのが応用です。ネットワークエラーなら「リトライボタンを表示」、認証エラーなら「ログイン画面へ誘導」、バリデーションエラーなら「該当フィールドにエラーメッセージを表示」——カスタムエラークラスを使えば、この分岐をシンプルに書けます。
用語