fetch の「タイムアウト対策」を一言でいうと
fetch のタイムアウト対策は、
「一定時間待ってもサーバーから返事がこなかったら、自分から通信を打ち切る仕組みを入れること」 です。
そのままの fetch には「〇秒で自動キャンセル」という機能はありません。
なので、自分で「これ以上は待たない」というルールを作ってあげる必要があります。
ここが重要です。
タイムアウト対策は、
「ユーザーを“無限待ち”から守るための優しさ」です。
ネット環境が悪いとき、サーバーが重いときでも、「何が起きているか分からないまま固まって見える」状態を避けるために入れます。
fetch にタイムアウト機能は標準でない、という前提
fetch は「いつか返ってくるまで待ち続ける」
普通に書くと、fetch はこんな感じです。
const response = await fetch("https://example.com/api/data");
JavaScriptサーバー側がものすごく遅かったり、
途中でレスポンスが詰まってしまった場合でも、
fetch 自体は「タイムアウト時間」を持っていません。
つまり、
いつまででも待つ(ブラウザや OS が切断しない限り)
→ ユーザーから見ると「画面が固まっているように見える」
ということが起きえます。
ここが重要です。
「fetch 自体にはタイムアウトがない」という前提をまず受け入れること。
そのうえで、「じゃあ自分で打ち切る仕組みをかぶせよう」という発想になります。
一番よく使うやり方:AbortController で中断する
AbortController って何者か
AbortController は、
「この fetch(など)を途中でキャンセルしたい」ときに使う道具 です。
やることは大きく分けて三つです。
AbortControllerを作る- その
controller.signalを fetch に渡す - 〇秒後に
controller.abort()を呼ぶ(=自分でタイムアウトを実装する)
これで、「〇秒経ったら fetch を中断する」が実現できます。
実際のコード例(3秒でタイムアウト)
async function fetchWithTimeout(url, timeoutMs = 3000) {
const controller = new AbortController();
const signal = controller.signal;
const timeoutId = setTimeout(() => {
controller.abort();
}, timeoutMs);
try {
const response = await fetch(url, { signal });
clearTimeout(timeoutId);
return response;
} catch (error) {
clearTimeout(timeoutId);
if (error.name === "AbortError") {
throw new Error("タイムアウトしました");
}
throw error;
}
}
JavaScriptこの関数がやっていることを、ゆっくり噛み砕きます。
const controller = new AbortController();
ここで「中断用リモコン」を作っています。
const signal = controller.signal;
この signal を fetch に渡すことで、
「この fetch は、あとから controller.abort() で止められるもの」になります。
setTimeout(() => { controller.abort(); }, timeoutMs);
timeoutMs ミリ秒後(ここでは 3000ms = 3秒)に、abort() が呼ばれるように予約しています。
つまり、3秒経ったらその fetch は強制的に中断されます。
const response = await fetch(url, { signal });
普通の fetch と同じですが、第二引数に { signal } を渡しています。
これがあることで、「signal に abort が飛んだら、この fetch も止まる」という動きになります。
clearTimeout(timeoutId);
fetch が無事に終わった場合は、
もうタイマーは不要なのでキャンセルしておきます。
catch (error) の中で、
if (error.name === "AbortError") { ... }
としているのは、
「abort されたときにだけ特別なメッセージ(タイムアウト)を出したい」からです。
実際に使うときのイメージ
上で作った fetchWithTimeout を使って、JSON を取るとこうなります。
async function loadData() {
try {
const response = await fetchWithTimeout("https://example.com/api/data", 3000);
if (!response.ok) {
throw new Error("HTTP エラー: " + response.status);
}
const data = await response.json();
console.log("成功:", data);
} catch (error) {
console.error("エラー:", error.message);
}
}
JavaScriptここが重要です。
AbortController を使ったタイムアウト実装は、
「一定時間経ったら自分でエラーを投げる」ためのひな形だと思ってください。
fetch に直で timeout を渡すのではなく、「signal を渡して abort する」のがポイントです。
Promise.race を使う別パターン(考え方の整理用)
「fetch vs タイムアウト用 Promise」の競争
もう一つの考え方として、Promise.race で「fetch」と「タイムアウト用の Promise」を競争させる 方法があります。
仕組み自体はこんなイメージです。
function timeoutPromise(ms) {
return new Promise((_, reject) => {
setTimeout(() => {
reject(new Error("タイムアウトしました"));
}, ms);
});
}
async function fetchWithTimeout(url, timeoutMs = 3000) {
return Promise.race([
fetch(url),
timeoutPromise(timeoutMs),
]);
}
JavaScriptPromise.race は、「配列の中の Promise のうち、一番早く解決(または reject)したものの結果を返す」関数です。
なので、
一方に fetch(url)
もう一方に「timeoutMs ms 後に reject する Promise」
を並べておくと、
時間内に fetch が終わればそれが採用され、
fetch が時間内に終わらなければ、タイムアウト側が勝って「タイムアウトエラー」が返ってきます。
この方法は概念としては分かりやすいですが、
fetch 自体を本当にキャンセルできるわけではない(裏側では続いていることがある)
AbortController と組み合わせたほうが「実際に中断できる」
などの理由で、実用には AbortController のほうがよく使われます。
ここが重要です。
「タイムアウトとは、“時間で reject する Promise を一つ足す” ことでも表現できる」
という考え方は、Promise を理解するうえでも役立ちます。
実際のブラウザ実装では AbortController を絡める、という位置づけで捉えてください。
タイムアウトしたとき、UI としてどう振る舞うか
ただ console.error するだけでは「無言」と同じ
タイムアウト対策は、コードレベルの話だけでは完結しません。
ユーザー視点では、
スピナーがくるくる回ったまま止まらない
「読み込み中…」が永遠に出たままになる
これが一番しんどい状態です。
タイムアウトが起きたときにやるべきことは例えばこうです。
読み込み中表示(ローディング表示)を止める。
「通信が混み合っています。しばらく待ってから再試行してください。」のようなメッセージを出す。
「再読み込み」ボタンを出す。
実装イメージを、ざっくり擬似コードにすると:
async function loadDataWithUI() {
setLoading(true);
try {
const response = await fetchWithTimeout("/api/data", 5000);
if (!response.ok) {
throw new Error("HTTP エラー: " + response.status);
}
const data = await response.json();
showData(data);
} catch (error) {
if (error.message.includes("タイムアウト")) {
showError("サーバーの応答がありません。時間をおいて再度お試しください。");
} else {
showError("エラーが発生しました。");
}
} finally {
setLoading(false);
}
}
JavaScriptここが重要です。
タイムアウト対策は、「5秒で abort すれば終わり」ではありません。
「タイムアウトしたことをユーザーにどう伝え、何を提案するか」まで含めて設計してあげて初めて意味を持ちます。
初心者として「タイムアウト対策」で本当に押さえてほしいこと
fetch は標準でタイムアウト時間を持っていない。
「勝手には切れない」ので、自分で「ここまで待ったら諦める」という仕組みを作る必要がある。
実用的な基本形は AbortController を使う方法。new AbortController() → signal を fetch に渡す → setTimeout で abort() を呼ぶ、の流れを押さえる。
abort() が呼ばれると fetch は AbortError で reject される。catch 側で error.name === "AbortError" を見れば、「これはタイムアウト(または手動キャンセル)だ」と判定できる。
タイムアウトは「技術的な中断」だけの話ではなく、
UI 上で「読み込み中を止める」「タイムアウトメッセージを出す」「再試行の導線を作る」といったユーザー体験の設計とセットで考える。
ここが重要です。
タイムアウト対策は、「何秒にするか」という数字の問題よりも、
“その秒数を超えたときに、自分のアプリはどう振る舞うべきか?” を決める作業です。
コードを書く前に、
「ユーザーはどれくらいなら待てそうか」
「タイムアウトしたとき、何ができると親切か」
を一度言葉にしてから AbortController や Promise を組み立ててみてください。
その一手間で、単なる通信制御が“気配りのあるアプリ”を支える仕組みに変わります。
