Encodera

Thanks for reading!

Donate to the site — we work hard for you.

How to add a timeout to a promise

Short answer

Promise.race() settles with the first promise that finishes. Race the task against a timer that rejects:

function withTimeout(promise, ms) {
  let timer;
  const timeout = new Promise((_, reject) => {
    timer = setTimeout(() => reject(new Error(`Timed out after ${ms} ms`)), ms);
  });
  return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
}
 
const delay = (ms, value) => new Promise((resolve) => setTimeout(() => resolve(value), ms));
 
console.log(await withTimeout(delay(50, 'fast answer'), 200));
 
try {
  await withTimeout(delay(500, 'slow answer'), 200);
} catch (error) {
  console.log(error.message);
}

Output:

fast answer
Timed out after 200 ms

For fetch

For requests, pass a signal: then the request itself is cancelled, not just the waiting:

const response = await fetch('/api/report', { signal: AbortSignal.timeout(3000) });

Common mistake

The timeout only stops the waiting. The slow operation itself keeps working in the background, and its result is simply ignored:

const slow = new Promise((resolve) =>
  setTimeout(() => {
    console.log('the slow task still finished');
    resolve();
  }, 300),
);
 
await Promise.race([slow, new Promise((resolve) => setTimeout(resolve, 100))]);
console.log('stopped waiting');
await new Promise((resolve) => setTimeout(resolve, 300));

Output:

stopped waiting
the slow task still finished

Functions used in this recipe

  • Promise.race — Returns the result of the first promise to settle — successful or not.
  • fetch — Sends an HTTP request and returns a promise of the response.

Thanks for reading!

Donate to the site — we work hard for you.