Javascript

JavaScript async/await エラーハンドリング完全ガイド【Node.js 22・Express】

ⓘ本ページはプロモーションが含まれています

Contents

スポンサードリンク

1. async/await の基本と try / catch パターン

ポイント

  • async 関数内部では必ず try / catch で例外を捕捉するのが最も直感的かつ安全。
  • await は Promise が reject された瞬間に JavaScript の例外としてスローされるため、同期コードと同様の構文でエラーハンドリングできる。

背景(Reason)

同期コード 非同期コード (async/await)
throwcatch で捕捉 await が reject → try / catch で捕捉

Promise.reject() をそのまま返すだけでは、呼び出し側が await せずに .then/.catch と混在させたとき に例外が分散し、意図しない未処理例外になることがあります。

実装例

補足:Promise.rejectthrow の違い

  • throw は即座に例外オブジェクトを生成し、await の直前で捕捉できる。
  • Promise.rejectマイクロタスク キューへ入れられるため、スタックトレースが若干ずれる点に注意。

旧環境(Node.js < 22)への代替案

機能 Node.js 22+ (ESM) Node.js < 22
Top‑level await ✅ 標準実装 --experimental-modules フラグか IIFE ((async () => { … })()) を使用
ES モジュール (import) ✅ デフォルト CJS (require) に書き換える、または Babel/ts-node でトランスパイル


2. トップレベルでのエラーハンドリングポリシー

ポイント

  • アプリ全体の例外は「トップレベル」(Express のエラーミドルウェア+プロセスレベルハンドラ)で一括管理する。
  • これにより、個々のビジネスロジックで過剰な try / catch を書く必要がなくなる。

背景(Reason)

  1. Express のエラーミドルウェア ((err, req, res, next)) はリクエスト単位の例外を自動的に集約。
  2. Node.js 22 の top‑level await でサーバ起動時の非同期例外も捕捉可能。
  3. process.on('unhandledRejection')process.on('uncaughtException') を併用すれば、予期しない例外を ロギング+プロセス終了 という安全なフローに落とし込める。

Node.js 22 の top‑level await と代替策

シナリオ 推奨実装
Node.js 22+ (ESM) await を直接記述
Node.js < 22 + ESM node --experimental-modules でフラグ有効化(非推奨)
Node.js < 22 + CJS (async () => { … })() の IIFE で包む
Babel/TS トランスパイル後に await__awaiter ヘルパーへ変換され、同等の動作になる

実装例:Express + top‑level await(Node.js 22+)

CJS(旧環境)での同等実装

まとめ

  • Express のエラーミドルウェアプロセスレベルハンドラ を組み合わせるだけで、ほとんどの例外が一元管理できる。
  • Node.js 22 が使えない環境でも IIFEBabel/TS のトランスパイル により同等のフローを実現可能。

3. fetch と async/await を組み合わせたネットワークエラー処理

ポイント

  • fetchHTTP ステータスが 4xx / 5xx でも resolve するので、Response.ok の判定と タイムアウト/キャンセル 用に AbortController を必ず組み合わせる。
  • Node.js 22 では globalThis.fetch が標準実装されているが、旧環境(Node.js < 18)では node-fetch 等のポリフィルを使用する。

背景(Reason)

状況 必要な対策
HTTP エラー (404, 500…) if (!response.ok) throw new Error(...)
ネットワーク障害・DNSエラー catch ブロックで捕捉
タイムアウト AbortController + setTimeout
ユーザー操作によるキャンセル 同上、signal.abort() を呼び出す

実装例:タイムアウト付き fetch ラッパー

使用例

AbortController を使ったリクエストキャンセル例(検索 UI)

旧環境(Node.js < 18)向けの代替策

ポイントnode-fetchAbortController を標準でサポートしているが、Node.js 16 以前では実装が不完全なことがあるため、必ずバージョンを確認する。

まとめ

  • fetch の結果は常に resolveResponse.ok で成功判定。
  • タイムアウト・キャンセルは AbortController が最もシンプルかつ標準的。
  • Node.js 22 未使用環境では node-fetch 等のポリフィルと同様のラッパーを書けば、挙動を揃えることができる。

4. Promise.all / Promise.allSettled を使った並列処理時のエラーハンドリング

ポイント

  • 全体成功が前提Promise.all(失敗したら即座に例外)。
  • 部分的成功でも結果を活用したいPromise.allSettled で個別ステータスを取得し、失敗情報だけを集約する。

背景(Reason)

手法 挙動
Promise.all 1 つでも reject すると 全体が reject。残りの Promise は実行は続くが結果は取得できない。
Promise.allSettled 全ての Promise が完了した後に {status, value/reason} の配列を返す。成功・失敗を個別に判定可能。

実装例:Promise.all とリトライ戦略

問題点

  • 失敗した URL が特定できない。
  • 他の成功したリクエストは結果として取得できず、無駄な再試行が発生しやすい。

Promise.allSettled で失敗情報を集約

補足:同時実行数の上限制御(p-limit

大量 URL を一括で Promise.all* に渡すと ネットワーク過負荷 が起きやすい。p-limit パッケージで同時実行数を絞る例:

まとめ

  • 全体成功が必須Promise.all + 必要に応じてリトライ。
  • 部分成功でも有用なデータがあるPromise.allSettled で失敗情報を集約し、ロギングや再試行の判断材料にする。
  • 同時実行数が多い場合は p-limit 等で制御 するとリソース消費を抑えられる。

5. カスタム Error クラス・型安全な catch と実務向け連携

ポイント

  • ビジネスドメインごとに 専用のエラークラス(例:ValidationError, NotFoundError)を作ることで、ハンドラ側で「何が起きたか」を明示的に判定できる。
  • TypeScript では 型ガード を併せて実装すれば、catch 内でプロパティへ安全にアクセス可能。

背景(Reason)

標準 Error カスタムエラー
メッセージだけ → 判別が困難 名前・固有プロパティ → ロギングや UI へのフィードバックが容易
スタックトレースは同じ エラーレベル(致命的/警告)をクラスで分離できる

実装例:TypeScript のカスタムエラーと型ガード

Winston + Sentry によるロギング・モニタリング(Node.js)

純粋 JavaScript(Node.js < 22)でも利用可能

Jest での async/await エラーハンドリングテスト例

  • expect(...).rejects により Promise が reject したことを簡潔に検証。
  • カスタムエラーのプロパティまでアサートできるので、ビジネスロジックの正確性が保証される。

まとめ

  • カスタムエラー + 型ガード → ハンドラで「何が失敗したか」=> ロギング・ユーザー通知が容易。
  • Winston + Sentry による一元ロギングは、Node.js 22 だけでなく旧バージョンでも同様に設定可能(CJS 版も用意)。
  • Jest の rejects アサーション は async/await のエラーハンドリングをテストする最短手段。

6. 総合まとめ

項目 推奨実装ポイント
基本的な例外捕捉 async 内は必ず try / catchawait が reject 時に自動で例外化される点を活かす。
トップレベルハンドリング Express のエラーミドルウェア + プロセスレベル (unhandledRejection, uncaughtException)。Node.js 22 では top‑level await、旧環境は IIFE または Babel/TS で代替。
ネットワーク通信 fetch はステータスチェック必須。AbortController によるタイムアウト/キャンセルを標準実装し、Node.js < 18 は node-fetch ポリフィルで同等化。
並列処理 全体成功が必要 → Promise.all(失敗は即例外)。部分成功でも結果活用 → Promise.allSettled+失敗情報集約。大量タスクは p-limit 等で同時実行数を制御。
カスタムエラー ビジネスドメインごとにクラス化し、型ガードで安全に判定。ロギングは Winston + Sentry、テストは Jest の rejects を使用。
互換性対策 Node.js 22 未使用時は IIFE, --experimental-modules, Babel/TS で top‑level await 相当を実装し、ESM と CJS の両方に対応できるようコードを分岐させる。

以上のベストプラクティスをプロジェクトに組み込むことで、非同期処理全体の例外が統一的に管理され、保守性・信頼性が飛躍的に向上します。コードレビュー時のチェックリストに「async/await → try/catch」「トップレベルエラーハンドラ有無」などを追加し、チーム全体で安全な非同期プログラミング文化を醸成してください。

スポンサードリンク

-Javascript