Javascript

fetch と Promise の基礎と AbortSignal.timeout を使ったタイムアウト・リトライ実装

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

スポンサードリンク

fetch と Promise の基本

fetch() はブラウザと Node.js(v18 以降)で利用できる標準的な HTTP クライアントです。
呼び出しは Promise\<Response> を返し、ネットワークエラーや CORS エラーが起きた場合は reject、サーバからレスポンスが正常に届いた場合は resolve されます。

ポイント
HTTP ステータスが 4xx/5xx でも Promise は成功 ( resolved ) とみなされるため、ステータスコードのチェックは必須です。
fetch() が返すオブジェクトはストリームベースなので、response.body の消費は一度だけ行う必要があります。

シンプルな使用例

  • response.oktrue のときはステータスが 200–299。
  • ネットワーク障害や CORS エラーは catch に流れる。

AbortController と AbortSignal.timeout によるタイムアウト実装

標準化と対応状況(2024 年時点)

環境 実装バージョン 備考
Chrome 115 (2023‑12) AbortSignal.timeout() が組み込み
Edge 115 (Chromium ベース) 同上
Firefox 119 (2024‑04) 実装済み(フラグ不要)
Safari 17.0 (2023‑09) 実装済み
Node.js v20 以降 globalThis.AbortSignal.timeout() が利用可能。
※ v18 では未実装で、ポリフィルが必要

AbortSignal.timeout(ms)WHATWG Fetch 標準 の一部として追加され、内部的に setTimeout と同等のタイマーを自動管理します。これにより手動で clearTimeout を忘れるリスクが排除されます。

手動実装との比較

項目 従来 (setTimeout) AbortSignal.timeout()
行数 5 行程度(controller, timer, clear) 1 行だけ signal: AbortSignal.timeout(ms)
タイマー管理 明示的に clearTimeout が必要 自動クリア、リーク防止
可読性 中程度 高い
フォールバック 常に使用可能 Node v20 未満では polyfill が必要

実装例

手動タイマー方式(フォールバック用)

AbortSignal.timeout() を使ったシンプル版(推奨)

注意
Node.js v20 未満でこのコードを実行する場合は、abort-controller パッケージや fetch-blob の polyfill を組み合わせて自前の AbortSignal.timeout() 相当処理を提供してください。


リトライロジックと指数バックオフ(jitter 付き)

基本方針

  1. 最大試行回数ベース遅延 をパラメータ化する。
  2. エラー種別に応じてリトライ可否を判定(AbortError, TypeError, HTTP 5xx 等)。
  3. 遅延は 指数バックオフ + jitter で算出し、同時リクエストが集中するスパイクを緩和する。

汎用関数(for ループ版)

再帰版(好みで選択可)

バックオフと jitter の意味

用語 計算式例 効果
指数バックオフ delay = baseDelay * 2^attempt(200 → 400 → 800 …) サーバ負荷が高まるたびに待機時間を伸ばす
jitter Math.random() * jitterMs(0‑100 ms のランダム) 同時リトライが揃う「スパイク」現象を分散させる

実務では baseDelayMs = 200, jitterMs = 100 がデフォルトとして広く採用されています。


Node.js(v20+)での実装と注意点

組み込み fetch と AbortSignal.timeout の利用

Node.js v20 以降はブラウザと同様に globalThis.fetchAbortSignal.timeout() がネイティブで提供されます。追加パッケージ (node-fetch 等) は不要です。

Node 固有のネットワーク遅延への対策

項目 ブラウザ側 Node.js 側の留意点
DNS ルックアップ OS が内部で処理し、fetch のタイミングに影響しにくい dns.lookup にもタイムアウト (lookupTimeout) を設定できる(Node 20+)
TCP 接続確立 ブラウザが自動的に再試行することがある http.Agent / https.AgentkeepAlivemaxSockets で制御可能
TLS 証明書エラー ユーザーに警告 UI が表示される rejectUnauthorized: false は開発時以外は使用しない。代わりに正しい証明書チェーンを提供

総合タイムアウト例(DNS + fetch を包括)

実務的な指針
全体の上限タイムアウトは 8 秒 程度に抑え、内部で fetch のタイムアウトを 5 秒* に分割すると、DNS エラーや TCP 接続遅延が原因の場合でも早期に失敗を検知できます。


エラーハンドリングのベストプラクティスとデバッグ手法

エラー種別別リトライ方針

種類 発生シーン リトライ推奨
AbortError タイムアウト、controller.abort() ✅(要件に応じて)
TypeError ネットワーク切断・DNS失敗
HTTP 5xx サーバ側一時障害
HTTP 429 レートリミット → 待機後再試行(バックオフと組み合わせ)
HTTP 4xx クライアントエラー(認証失敗等) ❌(基本的にリトライしない)

リソース解放の徹底

デバッグ手法

環境 推奨ツール・フラグ
ブラウザ Chrome/Edge DevTools → Network タブで canceled が表示されたら AbortErrorTiming パネルで DNS/TCP の待ち時間を可視化。
Node.js node --trace-warnings script.js で未処理 Promise 警告やタイムアウト情報を取得。
NODE_DEBUG=fetch 環境変数で内部ログ(リクエストヘッダー、ステータス)を出力できる。
テスト msw(Mock Service Worker)や nock でタイムアウト・ネットワーク障害シナリオを再現し、リトライロジックの単体テストを自動化する。

まとめ

  • fetch() は Promise ベースなので ステータスコードチェックエラーハンドリング が必須です。
  • AbortSignal.timeout(ms)(Chrome 115+, Edge, Firefox 119+, Safari 17, Node v20+)を利用すれば、手動タイマーの管理ミスによるリークを防げます。Node v18 以前では polyfill が必要です。
  • リトライは 最大試行回数 + 指数バックオフ + jitter の組み合わせで実装し、エラー種別に応じて shouldRetry ロジックで判定します。
  • Node.js 環境では DNS や TCP 接続遅延がタイムアウトの要因になるため、総合的なタイムアウト戦略を導入すると安全です。
  • エラーは必ず finally ブロックでリソース(タイマー・AbortController)を解放し、デバッグはブラウザ DevTools と Node のトレースフラグを併用して行うと効果的です。

これらのポイントを踏まえて実装すれば、ブラウザ・Node どちらでも 安全かつ保守性の高い HTTP 通信 が実現できます。

スポンサードリンク

-Javascript
-, , , , , , , ,