fix(web): bound stalled route queries

This commit is contained in:
lingniu
2026-07-16 07:18:49 +08:00
parent a0448d6615
commit d1764b3d66
2 changed files with 92 additions and 11 deletions

View File

@@ -85,16 +85,57 @@ type ApiErrorEnvelope = {
traceId?: string;
};
export const API_QUERY_TIMEOUT_MS = 15_000;
function queryTimeout(init: RequestInit | undefined) {
const upstream = init?.signal;
if (!upstream) return undefined;
const controller = new AbortController();
let timedOut = false;
const abortFromUpstream = () => controller.abort(upstream.reason);
if (upstream.aborted) abortFromUpstream();
else upstream.addEventListener('abort', abortFromUpstream, { once: true });
const timer = window.setTimeout(() => {
timedOut = true;
controller.abort();
}, API_QUERY_TIMEOUT_MS);
return {
init: { ...init, signal: controller.signal },
timedOut: () => timedOut,
cleanup: () => {
window.clearTimeout(timer);
upstream.removeEventListener('abort', abortFromUpstream);
}
};
}
function requestTimeoutError() {
const error = new Error(`请求超过 ${API_QUERY_TIMEOUT_MS / 1_000} 秒仍未完成,请检查网络后重试`);
error.name = 'ApiRequestTimeoutError';
return error;
}
async function request<T>(path: string, init?: RequestInit): Promise<T> {
const token = getAccessToken();
const requestInit = token ? { ...init, headers: withAuthorization(init?.headers, token) } : init;
const response = await fetch(path, requestInit);
if (!response.ok) {
if (response.status === 401 && token && path !== '/api/v2/session') notifyUnauthorizedSession(token);
throw new Error(await responseErrorMessage(response));
const timeout = queryTimeout(requestInit);
try {
const response = await fetch(path, timeout?.init ?? requestInit);
if (!response.ok) {
if (response.status === 401 && token && path !== '/api/v2/session') notifyUnauthorizedSession(token);
throw new Error(await responseErrorMessage(response));
}
const envelope = (await response.json()) as ApiEnvelope<T>;
if (timeout?.timedOut()) throw requestTimeoutError();
return envelope.data;
} catch (error) {
if (timeout?.timedOut() && (!(error instanceof Error) || error.name !== 'ApiRequestTimeoutError')) {
throw requestTimeoutError();
}
throw error;
} finally {
timeout?.cleanup();
}
const envelope = (await response.json()) as ApiEnvelope<T>;
return envelope.data;
}
function withAuthorization(headers: HeadersInit | undefined, token: string) {