fetchAPIはモダンな通信手段として広く使われていますが、デフォルトではタイムアウト機能がありません。そのため、遅延したサーバ応答や通信障害が発生するとアプリケーションが固まる可能性があります。この記事では、JavaScript fetch API タイムアウト 設定に関する目的、方法、対応ブラウザ、応用例などを網羅し、通信の安定性を高めるためのテクニックを詳しく解説します。fetchAPIの通信で困っている方に最適な内容です。
JavaScript fetch API タイムアウト 設定とは何か
JavaScript fetch API タイムアウト 設定は、fetch関数によるHTTP通信が特定の時間を超えて応答がない場合に通信を中断する仕組みを指します。デフォルトのfetchにはタイムアウトオプションが備わっておらず、通信がいつまでも応答を待ち続ける状態になることがあります。通信の安定化、ユーザー体験の向上、不要な通信リソースの節約などが目的となります。
この設定により、サーバ遅延やネットワークの不調、APIの応答遅延などが起きた際に、期限切れとして処理を中断し、エラーを返すことが可能になります。エラーはAbortErrorやTimeoutErrorで扱うことが一般的です。設定方法としては、AbortController+setTimeoutを使う古典的な方法、またはAbortSignal.timeoutという比較的新しい仕組みがあります。これらを理解して使い分けることが安定した通信に繋がります。
fetchAPIでのタイムアウトが必要な理由
通信が遅延するとユーザーは何が起きているかわからず、不安を感じることがあります。特にモバイル環境や不安定なネットワークでは顕著です。タイムアウトを設定することで、「遅い状態を明示的に検知」し、代替処理を実行することができます。結果としてアプリケーションの信頼性が向上します。通信が続きっぱなしになる無駄な帯域使用も防げます。
仕様上fetchにtimeoutオプションがない理由
fetchの仕様ではタイムアウトを直接指定するオプションが定義されていません。代わりに、通信の中止を目的としたAbortSignalという仕組みが用意されています。タイムアウトもこの仕組みを使って外部から中止シグナルを送る形で実現する設計です。このため、fetch(url, { timeout: 5000 }) のような指定は無視されます。
通信の中断とエラーの種類
fetchが中断された際、Promiseが拒否されます。AbortController.abort()を使った中断の場合はAbortErrorという名前のエラーになります。一方、AbortSignal.timeout()を使ってタイムアウトで中断された場合はTimeoutErrorという種類になります。これらを適切にキャッチして処理を分けることで、中断原因ごとに異なる対応が可能になります。
モダンな方法:AbortSignal.timeout()を使ったタイムアウト設定
AbortSignal.timeout()は新しい静的メソッドであり、指定したミリ秒後に自動的に中断シグナルを発生させます。これにより古いようなsetTimeoutとclearTimeoutの組み合わせが不要になり、コードが簡潔になります。signalオブジェクトをfetch関数に渡すことで、指定した時間内に応答がない場合にはTimeoutErrorをスローします。
書き方と構文
fetch(url, { signal: AbortSignal.timeout(ms) }) の形式で使用します。msはミリ秒で指定し、たとえば5000とすると5秒後にタイムアウトとなります。指定した時間が経過すると自動的に中断され、fetchのPromiseが拒否されます。応答が先に返ってきたら何も起きず通常通り処理されます。
エラー処理の分岐
AbortSignal.timeoutを使ったfetchでは、catchブロックでerr.nameをチェックしてTimeoutErrorかAbortErrorかを判断できます。またTypeErrorが発生する場合もあり、AbortSignal.timeoutがサポートされていない環境で使おうとするとこのエラーになることがあります。これら複数のエラー種類を扱うことで、より頑健なエラー処理が可能です。
対応ブラウザおよびランタイム
AbortSignal.timeoutは比較的新しい機能であり、モダンブラウザやNode.jsの最新バージョンでサポートされています。古いブラウザやNodeの旧バージョンでは未対応の場合があるので、使用前に環境のサポート情報を確認することが重要です。不対応環境ではポリフィルやAbortController+setTimeoutの組み合わせを fallback として用意しておくことが推奨されます。
従来的な方法:AbortControllerとsetTimeoutの組み合わせ
モダン機能が使えない環境や複数の中止要因を組み合わせたい場合は、AbortControllerとsetTimeoutを使用する方法が役立ちます。この方法ではコントローラーを作成し、一定時間後にabort()を呼び出すタイマーを設定し、fetchにはcontroller.signalを渡します。応答が返されたらタイマーをクリアします。タイムアウトの処理漏れを防ぐためのfinallyでの後片付けも欠かせません。
基本パターンのコード例
以下のようなパターンが一般的です。まずAbortControllerを作成し、signalを取得します。setTimeoutで指定時間後にcontroller.abort()を予約します。fetchにsignalを渡して実行し、通信が成功したらタイマーを解除します。もしタイムアウトになればabortされ、Promiseが拒否されます。
タイマー管理とメモリリーク回避
setTimeoutで設定したタイマーをクリアし忘れると、通信成功後にもタイマーが残り続け、不要なリソースを消費する可能性があります。finallyブロックやthen後にclearTimeoutを入れて確実に解除することが重要です。コードが乱立している場合やリクエストを頻繁に行う処理では特に注意が必要です。
ユーザー操作など他のキャンセル要因との組み合わせ
通信中にユーザーがキャンセルボタンを押す・ページを離れるなど、中断要因が複数ある場合があります。AbortControllerを共有したり multiple signals を扱うことで、それらを組み合わせる設計が可能です。setTimeoutだけでなく、イベントリスナー等のシグナルを利用し、必要に応じてAbortSignal.anyのような構成も考えられます。
具体例と応用:実践的なfetchタイムアウトの使い方
fetch通信でタイムアウトを使う場面として、外部API呼び出し、画像取得、ファイルダウンロードなどが挙げられます。特にUIでスピナーを表示しているとき、5秒や10秒を超えたら「応答が遅れています」などのメッセージを出す設計が望ましいです。タイムアウト時の見せ方を工夫することでユーザー起因の不安を軽減できます。
外部API呼び出しでの例
バックエンドのAPIを取得する処理でfetchを使用する場合、タイムアウトを設定するとサーバ遅延やタイムアウトエラーをキャッチしてエラー画面を表示したりリトライ処理を組み込めます。例えば5000msを超えたらabortし、「ネットワークが遅いため再試行します」といったUI提示と組み合わせることが可能です。
ファイルダウンロードや大きなデータ取得時の制御
大きなファイルをダウンロードする場合、応答は速くても読み込みに時間がかかる場合があります。その場合にはfetchの後にblob()やstream()などデータ加工の段階でAbortSignalを継続するか読み込みを中断するロジックを含める設計が求められます。また、TimeoutError発生後の後続処理が残らないようにすることが鍵です。
共通ユーティリティ関数として切り出す設計
複数箇所でfetchタイムアウトを使う場合は、fetchWithTimeoutのような共通関数を作成すると保守性が高まります。引数としてURL、タイムアウト時間、signalやオプションを受け取ると使い回しがしやすくなります。signalが渡された場合はそれとの競合を処理するなど柔軟性を持たせるとさらに良いです。
パフォーマンスと制約、注意点
タイムアウト設定は便利ですが、使い方を誤ると逆に不具合の原因になることがあります。まず、タイムアウトを短くしすぎると正常な通信でも頻繁に中断されてしまいます。逆に長すぎるとユーザーは待たされ続けるので体験が悪くなります。適切な値の設定が重要です。
不完全なタイムアウトとリソースの使用
例えばPromise.raceでfetchとタイマを競わせてタイムアウトを制御する方式は、fetch自体が中止されないため、内部で通信が続くことがあります。帯域や接続数の制限に影響するため、AbortControllerを使って通信自体を停止させる方法が推奨されます。
環境によるサポート状況の確認
AbortSignal.timeoutやAbortControllerは最新ランタイムで広くサポートされていますが、古いブラウザや古いNode.jsでは未対応です。polyfillを用いるか、従来のsetTimeout方式をフォールバックとして用意する必要があります。事前にブラウザ対応表やランタイムのバージョン目安を確認すると安全です。
UI/UXとエラーメッセージの設計
タイムアウトが発生した時には単にエラーを出すのではなく、「通信がタイムアウトしました。再試行しますか」「ネットワークを確認してください」といったユーザーに寄り添った情報を表示することが重要です。また、自動的にリトライを行う場合は指数バックオフを取り入れると過負荷を避けられます。
比較表:異なる実装方法のメリット・デメリット
以下の表で、AbortSignal.timeoutとAbortController+setTimeout、それぞれの特徴を比較し、用途に応じた選択をしやすくします。
| 方式 | 長所 | 短所 |
| AbortSignal.timeout() | コードが短い タイマーの後片付け不要 TimeoutErrorで中断理由が明確 |
未対応環境あり 複数の中止要因との組み合わせで制御が難しい場合あり |
| AbortController+setTimeout | あらゆる環境で利用可能 複数のシグナルや複雑な条件を組み込みやすい |
タイマー管理が煩雑 ミスでメモリやタイマーがリークする恐れあり |
まとめ
fetch通信におけるタイムアウト設定は、通信の信頼性とユーザー体験を大きく改善する重要技術です。デフォルトではタイムアウトがなく、通信がいつまでも待ち状態になる可能性があるため、遅延や障害に備えて適切な制御が求められます。
最新の方法としては AbortSignal.timeout を活用することでコードがシンプルになり、タイマーの後処理も自動化されます。古い環境や複雑な制御が必要な場面では AbortController と setTimeout の組み合わせが依然として有効です。用途に応じて両者を使い分け、必要ならば共通ユーティリティとして切り出すことで開発効率と保守性も向上します。
適切な時間(例えば 3〜10 秒など)を基準に設定し、エラーメッセージやリトライ処理をユーザー視点で整えることも重要です。これらのテクニックを用いることで、JavaScript fetch API の通信を安定化させ、快適なウェブアプリケーションを実現できます。
コメント