Skip to content

Retries

HTTP Client Toolkit supports opt-in automatic retries with exponential backoff and jitter for transient failures. Retries are disabled by default — pass a retry option to enable them.

When retries are enabled, these failures are automatically retried:

ConditionDescription
429Too Many Requests (rate limited)
500Internal Server Error
502Bad Gateway
503Service Unavailable
504Gateway Timeout
TypeErrorNetwork failures (DNS, connection refused, etc.)

Client errors (400, 401, 403, 404, 422) are not retried by default.

import { HttpClient } from '@http-client-toolkit/core';
const client = new HttpClient(stores, {
retry: {
maxRetries: 3, // Up to 3 retry attempts (4 total requests)
baseDelay: 1000, // 1 second initial delay
maxDelay: 30000, // Cap delay at 30 seconds
jitter: 'full', // Randomize delays to avoid thundering herd
},
});

Retry delays use exponential backoff: each attempt doubles the delay from baseDelay, capped at maxDelay.

With jitter: 'full' (default), the actual delay is randomized between 0 and the calculated backoff. This prevents multiple clients from retrying in sync (thundering herd).

With jitter: 'none', delays are deterministic:

AttemptDelay (baseDelay: 1000)
11000 ms
22000 ms
34000 ms
48000 ms

When a 429 response includes a Retry-After header, the server’s hint is used as a floor — the delay is max(backoff, retryAfterMs).

Override the default retry logic with retryCondition:

const client = new HttpClient(stores, {
retry: {
retryCondition: (context, attempt) => {
// Retry 404s (not retried by default)
if (context.statusCode === 404) return true;
// Stop retrying 500s after 2 attempts
if (context.statusCode === 500 && attempt > 2) return false;
// Fall back to default for everything else
return [429, 500, 502, 503, 504].includes(context.statusCode ?? 0);
},
},
});

Use onRetry to log or track retry attempts:

const client = new HttpClient(stores, {
retry: {
onRetry: (context, attempt, delay) => {
console.warn(
`Retry ${attempt} for ${context.url} (status ${context.statusCode}) in ${delay}ms`,
);
},
},
});
// Globally disabled
const client = new HttpClient(stores, { retry: false });
// Per-request disabled (even if constructor enables retries)
const data = await client.get(url, { retry: false });

Cache: Retries happen after a cache miss. Cached responses are never retried.

Deduplication: Retries occur within the dedup ownership — joiners wait for the final result (success or failure after all retries).

Rate Limiting: Store-based rate limiting and server cooldowns are enforced before the retry loop begins. The Retry-After header from 429 responses is respected within the retry delay calculation.

Stale-if-error: If all retries are exhausted and a stale cache entry exists with stale-if-error, the stale value is returned as a fallback.

Interceptors: Both requestInterceptor and responseInterceptor run on every attempt, so refreshed auth tokens are picked up automatically.

AbortSignal: Aborting the signal cancels both the current fetch and any pending retry delay.