Implementing Timeout with Promises in JavaScript
Timeout is a common use case for Promise.race. Here is how to implement it robustly.
Implementing Timeout with Promises in JavaScript
Implementing a timeout is a common use case for Promise.race. Here is how to do it robustly.
Basic Timeout
function withTimeout(promise, ms) { const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), ms) ); return Promise.race([promise, timeout]); } const result = await withTimeout(fetch("/api"), 5000); `` If `fetch` takes more than 5 seconds, the timeout rejects first, and the race rejects. ### Custom Timeout Error ```js class TimeoutError extends Error { constructor(ms) { super(`Operation timed out after ${ms}ms`); this.name = "TimeoutError"; } } function withTimeout(promise, ms) { return Promise.race([ promise, new Promise((_, reject) => setTimeout(() => reject(new TimeoutError(ms)), ms)), ]); } `` ### Timeout with AbortController (Cancelling the Fetch) ```js async function fetchWithTimeout(url, ms) { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), ms); try { const res = await fetch(url, { signal: controller.signal }); return res; } finally { clearTimeout(timeout); } } `` This actually cancels the fetch (not just races it). The `AbortController` aborts the fetch request. ### Timeout for Any Promise ```js async function withTimeout(promise, ms) { let timer; const timeout = new Promise((_, reject) => { timer = setTimeout(() => reject(new Error("timeout")), ms); }); try { const result = await Promise.race([promise, timeout]); clearTimeout(timer); return result; } catch (err) { clearTimeout(timer); throw err; } } `` This clears the timer whether the promise resolves or rejects (no memory leak). ### The Takeaway Implement timeout with `Promise.race` (race the operation against a timeout promise). Use `AbortController` to actually cancel `fetch` (not just race). Use `clearTimeout` to clean up the timer (avoid memory leaks). Create a custom `TimeoutError` for specific error handling.
Use Promise.race: race the operation against a timeout promise. Promise.race([fetch('/api'), new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 5000))]). If the timeout fires first, the race rejects.
Use AbortController: const controller = new AbortController(); fetch(url, { signal: controller.signal }). Set a timeout to call controller.abort(). This actually cancels the fetch, not just races it.
To avoid a memory leak. The timeout promise's timer keeps running even after the race settles. clearTimeout cancels the timer. Use try/finally to clear it regardless of outcome.
No. Promise.race does not cancel the other promises. They continue running; their results are just ignored. For actual cancellation, use AbortController (with fetch) or implement your own cancellation logic.
Create a class extending Error: class TimeoutError extends Error { constructor(ms) { super(`timed out after ${ms}ms`); this.name = 'TimeoutError'; } }. Then reject with new TimeoutError(ms) in the timeout promise.
Ready to master React completely?
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course to dive deeper with high-quality video tutorials, solve interview questions, and a premium community.
Master React
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course.

