タイムアウト実行とは何を守るための仕組みなのか
「タイムアウト実行」は、
“一定時間以内に終わらなかった処理を強制的に中断し、失敗として扱う”
ためのユーティリティです。
業務システムでは、次のような問題が頻繁に起きます。
- 外部 API が返ってこない
- ネットワークが不安定で応答が遅い
- 重い処理が予想以上に時間を食ってしまう
- いつまでも待ち続けると画面が固まる
こうした「終わらない処理」を放置すると、ユーザー体験もシステムの安定性も大きく損なわれます。
そこで必要になるのが タイムアウト実行。
「この処理は最大 3 秒だけ待つ。それ以上かかったら強制的に失敗扱いにする」
という安全装置をつけるイメージです。
Promise をタイムアウト付きで実行する基本ユーティリティ
まずは最もシンプルな形を見てみる
function withTimeout(promise, ms) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error(`Timeout after ${ms}ms`));
}, ms);
promise
.then((value) => {
clearTimeout(timer);
resolve(value);
})
.catch((err) => {
clearTimeout(timer);
reject(err);
});
});
}
JavaScriptこの withTimeout は、
「指定した時間以内に終わらなければ reject する Promise」
を返す関数です。
動きはこうです。
setTimeoutで「ms ミリ秒後に強制エラー」を予約する- 本来の Promise が先に成功したら、タイマーを解除して resolve
- 本来の Promise が先に失敗したら、タイマーを解除して reject
- どちらも起きず ms が経過したら、タイムアウトエラーを投げる
これがタイムアウト実行の基本構造です。
実際に API 呼び出しに使ってみる
例えば、ユーザー情報を取得する API があるとします。
async function fetchUser() {
const res = await fetch("/api/user");
return res.json();
}
JavaScriptこれをタイムアウト付きで呼ぶにはこうします。
async function loadUser() {
const user = await withTimeout(fetchUser(), 3000);
console.log("取得成功:", user);
}
JavaScriptこのコードの意味は、
fetchUser()が 3 秒以内に終われば成功- 3 秒経っても返ってこなければ
Timeout after 3000msで失敗
という動きになります。
業務では「API が遅いときに永遠に待つ」ことが最も危険なので、
このように「最大待ち時間」を決めるのは非常に重要です。
async 関数を直接タイムアウト付きで実行する版
Promise を渡すのではなく「関数」を渡したい場合
次のようなユーティリティもよく使われます。
async function runWithTimeout(fn, ms) {
return withTimeout(fn(), ms);
}
JavaScript使い方はこうです。
const result = await runWithTimeout(() => fetch("/api/data"), 2000);
JavaScriptこの形のメリットは、
「関数を渡すだけでタイムアウト付き実行ができる」
という点です。
タイムアウト実行の重要ポイント:中断できるわけではない
ここで初心者がよく誤解するポイントがあります。
タイムアウト実行は「処理を止める」わけではありません。
“待つのをやめる”だけです。
例えば、fetch は途中で中断されません。
内部ではまだ通信が続いている可能性があります。
本当に中断したい場合は、AbortController を使います。
AbortController を使った「本当に中断する」タイムアウト実行
fetch をタイムアウトで中断する実務的な書き方
async function fetchWithTimeout(url, ms) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), ms);
try {
const res = await fetch(url, { signal: controller.signal });
return res;
} finally {
clearTimeout(timer);
}
}
JavaScript使い方はこうです。
try {
const res = await fetchWithTimeout("/api/user", 3000);
const data = await res.json();
console.log("成功:", data);
} catch (err) {
console.error("タイムアウトまたは中断:", err.message);
}
JavaScriptここでの深掘りポイントは、
AbortControllerが fetch を本当に中断してくれる- タイムアウト後は通信自体が止まる
- サーバー側の負荷も減らせる
という点です。
業務では「中断できるタイムアウト」が必要な場面が多いので、
このパターンは非常に実用的です。
タイムアウト実行と再試行処理の組み合わせ
実務では、タイムアウトと再試行を組み合わせることが多いです。
例えば、
- 3 秒以内に返ってこなければタイムアウト
- 最大 3 回まで再試行
- それでもダメならエラー
というような制御です。
async function fetchUserSafely() {
return retry(
() => fetchWithTimeout("/api/user", 3000),
{ maxAttempts: 3, delayMs: 1000 }
);
}
JavaScriptこれで、
- 遅い API は 3 秒で切る
- 一時的な障害なら再試行で回復
- 永遠に待つこともない
という「しぶとくて安全な処理」が実現できます。
実務での具体的な利用イメージ
ローディング画面の制御
「API が遅すぎるときはローディングを消してエラー表示に切り替える」
という UI 制御はよくあります。
async function loadData() {
try {
const data = await withTimeout(fetch("/api/data"), 5000);
render(data);
} catch {
showError("通信がタイムアウトしました");
}
}
JavaScriptユーザー体験を守るためにも、タイムアウトは必須です。
バッチ処理の安全装置
バッチ処理で「一つのタスクが永遠に終わらない」問題は致命的です。
async function runTaskSafely(task) {
return runWithTimeout(task, 10000); // 10秒以内に終わらなければ失敗扱い
}
JavaScriptこれにより、バッチ全体が止まる事故を防げます。
小さな練習で感覚をつかむ
次のような「わざと遅い処理」を作って、
タイムアウト実行を試してみてください。
function slowTask() {
return new Promise((resolve) => {
setTimeout(() => resolve("完了"), 5000);
});
}
async function test() {
try {
const result = await withTimeout(slowTask(), 2000);
console.log(result);
} catch (err) {
console.log("タイムアウト:", err.message);
}
}
test();
JavaScript「5 秒かかる処理を 2 秒で打ち切る」という動きを体感すると、
タイムアウト実行の重要性がよく分かります。
必要であれば、
「中断できるタイムアウト」
「タイムアウト+再試行」
「タイムアウト付きの並列処理」
など、さらに実務寄りの応用パターンも解説できます。
