| /** | |
| * Relay abort without forwarding the Event argument as the abort reason. | |
| * Using .bind() avoids closure scope capture (memory leak prevention). | |
| */ | |
| function relayAbort(this: AbortController) { | |
| this.abort(); | |
| } | |
| /** Returns a bound abort relay for use as an event listener. */ | |
| export function bindAbortRelay(controller: AbortController): () => void { | |
| return relayAbort.bind(controller); | |
| } | |
| /** | |
| * Fetch wrapper that adds timeout support via AbortController. | |
| * | |
| * @param url - The URL to fetch | |
| * @param init - RequestInit options (headers, method, body, etc.) | |
| * @param timeoutMs - Timeout in milliseconds | |
| * @param fetchFn - The fetch implementation to use (defaults to global fetch) | |
| * @returns The fetch Response | |
| * @throws AbortError if the request times out | |
| */ | |
| export async function fetchWithTimeout( | |
| url: string, | |
| init: RequestInit, | |
| timeoutMs: number, | |
| fetchFn: typeof fetch = fetch, | |
| ): Promise<Response> { | |
| const controller = new AbortController(); | |
| const timer = setTimeout(controller.abort.bind(controller), Math.max(1, timeoutMs)); | |
| try { | |
| return await fetchFn(url, { ...init, signal: controller.signal }); | |
| } finally { | |
| clearTimeout(timer); | |
| } | |
| } | |