fix(infra): avoid detached finally unhandled rejection in fetch wrapper (#18014)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 4ec21c89cb
Co-authored-by: Jackten <2895479+Jackten@users.noreply.github.com>
Co-authored-by: sebslight <19554889+sebslight@users.noreply.github.com>
Reviewed-by: @sebslight
This commit is contained in:
Jackten
2026-02-16 09:17:23 -04:00
committed by GitHub
parent cb391f4bdc
commit e3e8046a93
3 changed files with 152 additions and 5 deletions

View File

@@ -45,18 +45,32 @@ export function wrapFetchWithAbortSignal(fetchImpl: typeof fetch): typeof fetch
}
const controller = new AbortController();
const onAbort = bindAbortRelay(controller);
let listenerAttached = false;
if (signal.aborted) {
controller.abort();
} else {
signal.addEventListener("abort", onAbort, { once: true });
listenerAttached = true;
}
const response = fetchImpl(input, { ...patchedInit, signal: controller.signal });
if (typeof signal.removeEventListener === "function") {
void response.finally(() => {
const cleanup = () => {
if (!listenerAttached || typeof signal.removeEventListener !== "function") {
return;
}
listenerAttached = false;
try {
signal.removeEventListener("abort", onAbort);
});
} catch {
// Foreign/custom AbortSignal implementations may throw here.
// Never let cleanup mask the original fetch result/error.
}
};
try {
const response = fetchImpl(input, { ...patchedInit, signal: controller.signal });
return response.finally(cleanup);
} catch (error) {
cleanup();
throw error;
}
return response;
}) as FetchWithPreconnect;
const fetchWithPreconnect = fetchImpl as FetchWithPreconnect;