gendesign/frontend/src/lib/abortableSleep.ts
Light1YT 16e505326d
Some checks failed
Deploy / deploy (push) Blocked by required conditions
Deploy / changes (push) Successful in 6s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy / build-frontend (push) Has been cancelled
perf(site-finder): honour AbortSignal в poll-цикле useParcelAnalyzeQuery (#1248)
queryFn игнорировал TanStack v5 AbortSignal: first POST /analyze, poll GET
/fetch-status и re-POST не передавали signal, цикл спал на non-abortable
setTimeout без проверки signal.aborted. При unmount / смене cad|horizon цикл
жил до 2-мин кэпа, дёргая GET /fetch-status каждые 2с + POST /analyze на каждый
ready — зомби-поллинг. Проброс signal во все 3 fetch, sleep через shared
abortableSleep (extracted из useSiteAnalysis #1242), throw AbortError перед
итерацией. +3 vitest (abort + happy-path).

Closes #1248
2026-06-13 13:19:50 +00:00

32 lines
1.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Sleep for `ms`, but resolve early if the AbortSignal fires.
*
* Используем в poll-циклах (analyze / fetch-status) чтобы цикл пробуждался
* сразу при abort (unmount, смена cad/horizon, cancel), а не ждал полные `ms`
* до следующей проверки `signal.aborted`. Без этого осиротевший цикл живёт до
* hard-cap (60 × 2s = 2 мин), сыпя GET /fetch-status + POST /analyze — зомби-
* поллинг (#1248, sibling #1242).
*
* Резолвится (не реджектится) — вызывающий сам решает бросать ли AbortError,
* проверив `signal.aborted` после await.
*/
export function abortableSleep(
ms: number,
signal?: AbortSignal,
): Promise<void> {
return new Promise((resolve) => {
if (signal?.aborted) {
resolve();
return;
}
const onAbort = () => {
clearTimeout(timer);
resolve();
};
const timer = setTimeout(() => {
signal?.removeEventListener("abort", onAbort);
resolve();
}, ms);
signal?.addEventListener("abort", onAbort, { once: true });
});
}