Promise TimeoutLoading saved progress…

Promise Timeout

Implement promiseTimeout(promise, ms) — a wrapper that gives any pending promise a deadline. If the input settles within ms milliseconds, the returned promise settles with the same outcome (fulfilment or rejection). If the deadline arrives first, the returned promise rejects with a timeout Error. It's the standard composition you reach for whenever an async operation might hang — a slow fetch, a stalled database call, a flaky third-party SDK.

The exercise is short on lines but easy to get subtly wrong: the timer must be cancelled when the input wins, or you leak a setTimeout that fires after the work is done and produces a stray rejection no one is listening for.

Signature

// Wraps `promise` with a deadline of `ms` milliseconds.
// Resolves/rejects with the input's outcome if it settles first;
// rejects with an Error if `ms` elapses first.
function promiseTimeout<T>(promise: T | PromiseLike<T>, ms: number): Promise<T>;

Examples

// Promise resolves before the deadline — passes through.
const fast = new Promise((r) => setTimeout(() => r('hello'), 20));
promiseTimeout(fast, 100).then(console.log); // 'hello'
// Promise is slower than the deadline — rejects with a timeout Error.
const slow = new Promise((r) => setTimeout(() => r('hello'), 200));
promiseTimeout(slow, 50).catch((err) => console.log(err.message));
// 'Promise timed out after 50ms'
// Promise rejects in time — the rejection passes through unchanged.
const bad = new Promise((_, reject) => setTimeout(() => reject('boom'), 10));
promiseTimeout(bad, 100).catch(console.log); // 'boom'

Notes

  • Return a real Promise. Tests check instanceof Promise. Don't return the input directly, even when it would settle in time.
  • The timeout reason must be an Error. A descriptive message like 'Promise timed out after 50ms' is expected. String rejections are a common mistake — they lose the stack.
  • Cancel the timer when the input wins. If promise settles before ms, you must clearTimeout the deadline timer. Leaving it scheduled leaks a handle and fires a late rejection that no .catch is listening for.
  • Non-promise inputs are valid. A plain value like 42 or 'hello' should be wrapped through Promise.resolve so the same code path handles every case — including thenables.
  • ms === 0 is allowed. An already-resolved input still wins the race (microtask beats macrotask). Don't special-case zero.
  • Don't use AbortSignal.timeout or libraries. setTimeout, clearTimeout, Promise.race, and Promise.resolve are all you need.
Loading editor…