api: separate timeout abort retries from intentional aborts

This commit is contained in:
Scott Idem
2026-05-21 15:46:30 -04:00
parent d5d552a029
commit f5cf1ef398
3 changed files with 68 additions and 6 deletions

View File

@@ -205,7 +205,12 @@ export const get_object = async function get_object({
// independent timeout. Sharing a single controller across retries leaves
// retries unprotected once the first attempt's clearTimeout() runs.
const controller = new AbortController();
// Track whether THIS helper's timeout fired. AbortError alone is ambiguous:
// it can mean timeout OR intentional caller abort (navigation/unmount).
// We only retry timeout-aborts; intentional aborts should fail fast.
let did_timeout_abort = false;
const timeoutId = setTimeout(() => {
did_timeout_abort = true;
console.warn(`API GET: Request timed out after ${timeout}ms (attempt ${attempt}/${retry_count}).`);
controller.abort();
}, timeout);
@@ -245,9 +250,16 @@ export const get_object = async function get_object({
(response.name === 'TypeError' ||
response.name === 'AbortError'))
) {
// AbortError = intentional cancel: our own timeout fired, or the caller
// aborted (e.g. component unmounted, user navigated away). Never retry.
if (response.name === 'AbortError') return false;
// AbortError can be either timeout or intentional abort.
// Retry only helper-owned timeout aborts; fail fast on caller abort.
if (response.name === 'AbortError') {
if (did_timeout_abort) {
throw new Error(
`Timeout abort (attempt ${attempt}/${retry_count}) after ${timeout}ms`
);
}
return false;
}
// TypeError = transient network failure (ERR_NETWORK_CHANGED,
// ERR_NETWORK_IO_SUSPENDED, hotel/conference WiFi blip, etc.).