api: separate timeout abort retries from intentional aborts
This commit is contained in:
@@ -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.).
|
||||
|
||||
@@ -203,7 +203,11 @@ export const post_object = async function post_object({
|
||||
// Declared at loop scope (not inside try) so the catch block can clearTimeout.
|
||||
// Fresh controller per attempt — same rationale as api_get_object.ts.
|
||||
const controller = new AbortController();
|
||||
// AbortError is not specific enough by itself. Distinguish timeout-aborts
|
||||
// (retryable transient class) from intentional caller aborts (fail-fast).
|
||||
let did_timeout_abort = false;
|
||||
const timeoutId = setTimeout(() => {
|
||||
did_timeout_abort = true;
|
||||
console.warn(`API POST: Request timed out after ${timeout}ms (attempt ${attempt}/${retry_count}).`);
|
||||
controller.abort();
|
||||
}, timeout);
|
||||
@@ -254,9 +258,16 @@ export const post_object = async function post_object({
|
||||
(response.name === 'TypeError' ||
|
||||
response.name === 'AbortError'))
|
||||
) {
|
||||
// AbortError = intentional cancel (timeout fired, or caller aborted).
|
||||
// Never retry — the abort was deliberate.
|
||||
if (response.name === 'AbortError') return false;
|
||||
// Retry timeout-aborts from this helper; do not retry caller aborts
|
||||
// (route change/unmount/manual cancellation).
|
||||
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. Throw into the retry loop
|
||||
// so backoff-and-retry applies. Same fix as api_get_object.ts — see
|
||||
|
||||
Reference in New Issue
Block a user