Pass AbortSignal.timeout() as the request’s signal. The fetch rejects with a TimeoutError when the time is up.
const res = await fetch(url, { signal: AbortSignal.timeout(5000) });
Catch it by name if you want to tell a timeout apart from a network failure:
try {
const res = await fetch(url, { signal: AbortSignal.timeout(5000) });
return await res.json();
} catch (err) {
if (err.name === "TimeoutError") throw new Error(`${url} timed out after 5s`);
throw err;
}
Cancel for another reason too
When the timeout has to combine with a user cancel or a shutdown signal, use AbortSignal.any():
const signal = AbortSignal.any([AbortSignal.timeout(5000), controller.signal]);
Node 20.3 and up, and current browsers.
Gotchas
- Without a signal, Node’s fetch has no overall timeout. A server that accepts the connection and never answers holds your request open until the socket idles out, which can be minutes.
- The timeout covers the headers and the body.
await res.json()on a slow body can still throwTimeoutError, so keep the try around both. AbortSignal.timeoutfires once and can’t be reset. For retries, make a new signal per attempt.- Older code creates an
AbortControllerand asetTimeoutby hand. It works, but forgettingclearTimeouton success leaks a timer per request.AbortSignal.timeouthas no such cleanup.