/** * Race a promise against a timeout. Returns `null` if the timeout * fires before the promise settles. */ export const withTimeout = async (promise: Promise, timeoutMs: number) => await Promise.race([ promise, new Promise((resolve) => { setTimeout(() => resolve(null), timeoutMs); }), ]); /** * Wrapper around `fetch` that aborts the request after `timeoutMs`. * Throws with a descriptive message on timeout. */ export const fetchWithTimeout = async (input: string | URL | Request, init: RequestInit & { timeoutMs: number }) => { const { timeoutMs, ...fetchInit } = init; const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), timeoutMs); try { return await fetch(input, { ...fetchInit, signal: controller.signal }); } catch (err) { if (err instanceof DOMException && err.name === 'AbortError') { throw new Error(`Request timed out after ${timeoutMs}ms`); } throw err; } finally { clearTimeout(timeout); } };