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.
// 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>;
// 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'
instanceof Promise. Don't return the input directly, even when it would settle in time.Error. A descriptive message like 'Promise timed out after 50ms' is expected. String rejections are a common mistake — they lose the stack.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.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.AbortSignal.timeout or libraries. setTimeout, clearTimeout, Promise.race, and Promise.resolve are all you need.