export type RequestAbortCause = "external" | "timeout"; export interface RequestAbortContext { readonly signal?: AbortSignal; /** 返回第一个触发请求中止的来源。 */ getCause(): RequestAbortCause | undefined; /** 清理定时器和外部信号监听器。 */ dispose(): void; } /** 创建可判定首个中止来源且可显式清理的请求信号。 */ export function createRequestAbortContext( timeoutMs: number | null, externalSignal?: AbortSignal, ): RequestAbortContext { if (timeoutMs === null && externalSignal === undefined) { return { signal: undefined, getCause: () => undefined, dispose: () => undefined, }; } const controller = new AbortController(); let cause: RequestAbortCause | undefined; let timeoutHandle: ReturnType | undefined; /** 只记录并传播第一个中止来源。 */ const abort = (nextCause: RequestAbortCause, reason?: unknown): void => { if (cause !== undefined) { return; } cause = nextCause; controller.abort(reason); }; /** 将外部取消信号传播到独立请求控制器。 */ const handleExternalAbort = (): void => { abort( "external", externalSignal?.reason ?? new DOMException("Request was cancelled.", "AbortError"), ); }; if (externalSignal?.aborted) { handleExternalAbort(); } else { externalSignal?.addEventListener("abort", handleExternalAbort, { once: true }); } if (timeoutMs !== null && cause === undefined) { timeoutHandle = setTimeout(() => { timeoutHandle = undefined; abort( "timeout", new DOMException(`Request timed out after ${timeoutMs} ms.`, "TimeoutError"), ); }, timeoutMs); timeoutHandle.unref?.(); } return { signal: controller.signal, getCause: () => cause, dispose: () => { if (timeoutHandle !== undefined) { clearTimeout(timeoutHandle); timeoutHandle = undefined; } externalSignal?.removeEventListener("abort", handleExternalAbort); }, }; }