概要
エラーハンドリングとは「止める」ことではない。
それは**「壊さず、知らせ、回復可能な構造にする」ための設計行為**である。
JavaScriptの実行環境は多様で不確実。
同期・非同期・UI・ネットワークあらゆる箇所で例外が発生しうる。
本稿では、予期可能な失敗の吸収と通知、UIの安全な設計、ログによる分析性の担保を含めた、壊れないアプリケーション設計を提示する。
1. try-catch の適用範囲は最小単位に
try {
const data = JSON.parse(raw);
} catch (e) {
console.error('JSONパース失敗:', e);
}
- ✅ try-catchの範囲を小さくすることで局所的にエラーを制御
- ❌ アプリ全体を包むようなcatch構造 → 障害原因が特定不能に
2. 非同期関数での明示的エラーフロー設計
async function fetchUser() {
try {
const res = await fetch('/api/user');
if (!res.ok) throw new Error('通信エラー');
return await res.json();
} catch (e) {
throw new Error(`ユーザー取得失敗: ${e.message}`);
}
}
- ✅ ネットワークの成功でもres.okをチェック
- ✅ エラーを補足して明示的なメッセージ化
3. ユーザー通知と開発ログは目的を分ける
✅ ユーザーには簡潔に
showError('サーバーと通信できませんでした');
✅ 開発者向けには詳細ログ
console.error('Fetch failed:', e, context);
→ ✅ ユーザーの混乱を防ぎつつ、調査可能な情報を記録
4. グローバル例外フックの設計
window.onerror = function (msg, url, line, col, error) {
sendLog({ msg, url, line, col, stack: error?.stack });
};
window.onunhandledrejection = function (event) {
sendLog({ reason: event.reason });
};
- ✅ 未処理の例外・Promiseエラーも捕捉可能
- ✅ ログに加え、ユーザーへの再読み込み提案なども実装可能
5. リトライ戦略と安全なフォールバック
async function retry(fn, limit = 3) {
for (let i = 0; i < limit; i++) {
try {
return await fn();
} catch (e) {
if (i === limit - 1) throw e;
}
}
}
- ✅ 通信系の一時的失敗に対して限定的な再試行
- ✅ 最後にはフォールバックデータを表示 or 再接続ボタンを提供
6. 意図的な失敗の伝播設計
function validateInput(value) {
if (!value) throw new ValidationError('入力が空です');
}
- ✅ **ドメインエラー(入力ミス・状態不整合)**は意図的にthrow
- ✅
ValidationError
,NotFoundError
などカスタムエラーで粒度を明示
設計判断フロー
① try-catchの範囲が広すぎないか? → 小さい関数単位で設計
② catch後にログとユーザー通知を分けているか? → 目的を区別
③ ネットワーク失敗が検知可能になっているか? → res.okチェック
④ 再試行やフォールバックが存在しているか? → retry構造を用意
⑤ 予期しうるエラーを例外として設計しているか? → カスタムエラー導入
よくあるミスと対策
❌ try-catchで握りつぶして何も通知しない
→ ✅ ログ送信 + UIで明示的に通知
❌ 非同期エラーが await
されず、気づかない
→ ✅ Promiseを必ずawait or catchで制御
❌ 例外の種類が曖昧で、処理分岐ができない
→ ✅ カスタムエラーで意味付けを明確に
結語
エラーハンドリングとは「失敗を防ぐこと」ではない。
それは**“失敗しても壊れない構造”をつくるための設計戦略**である。
- エラーは発生する前提で設計し
- 意図的に分類し
- 最小限に影響を閉じ込める
JavaScriptにおけるエラーハンドリング設計とは、
“失敗を吸収し、回復可能にするための構造化された防御”である。