You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
46 lines
1.3 KiB
46 lines
1.3 KiB
/** Default cap for HTTP fetches so tabs cannot hang indefinitely on bad networks or servers. */ |
|
const DEFAULT_FETCH_TIMEOUT_MS = 30_000 |
|
|
|
/** |
|
* `fetch` with a wall-clock timeout. Honors an optional caller `signal` (abort propagates both ways). |
|
*/ |
|
export async function fetchWithTimeout( |
|
input: RequestInfo | URL, |
|
init: RequestInit & { timeoutMs?: number } = {} |
|
): Promise<Response> { |
|
const { timeoutMs = DEFAULT_FETCH_TIMEOUT_MS, signal: userSignal, ...rest } = init |
|
const controller = new AbortController() |
|
|
|
let timeoutId: ReturnType<typeof setTimeout> | null = setTimeout(() => { |
|
timeoutId = null |
|
controller.abort() |
|
}, timeoutMs) |
|
|
|
const onUserAbort = () => { |
|
if (timeoutId !== null) { |
|
clearTimeout(timeoutId) |
|
timeoutId = null |
|
} |
|
controller.abort() |
|
} |
|
|
|
if (userSignal) { |
|
if (userSignal.aborted) { |
|
if (timeoutId !== null) { |
|
clearTimeout(timeoutId) |
|
timeoutId = null |
|
} |
|
throw new DOMException('The operation was aborted.', 'AbortError') |
|
} |
|
userSignal.addEventListener('abort', onUserAbort, { once: true }) |
|
} |
|
|
|
try { |
|
return await fetch(input, { ...rest, signal: controller.signal }) |
|
} finally { |
|
if (timeoutId !== null) { |
|
clearTimeout(timeoutId) |
|
} |
|
userSignal?.removeEventListener('abort', onUserAbort) |
|
} |
|
}
|
|
|