-
Notifications
You must be signed in to change notification settings - Fork 0
Cookbook: retries
The retry service re-issues failed requests with exponential backoff — but only when it is safe to do so. It is attached by default and off until a request asks for retries. A retryable failure is a network error, a 5xx, or a 429.
Set retries to the number of extra attempts. Without it, nothing is retried.
import io from 'double-meh';
// initial call + up to 3 retries
const data = await io.get('https://api.example.com/flaky', null, {retries: 3});Retries only fire for requests that are safe to repeat. GET, HEAD, OPTIONS, PUT, and DELETE are always retried. A bare POST is not — a network blip could mean the first attempt already succeeded, so blindly resending risks a duplicate.
// not retried: repeating a POST could create two resources
await io.post('https://api.example.com/orders', order, {retries: 3});Give the server a way to deduplicate. idempotencyKey: true generates a UUID once and reuses it across every retry; a string uses your own key. (ifNoneMatch: '*' also unlocks retries for create-if-absent POSTs.)
await io.post('https://api.example.com/orders', order, {
retries: 3,
idempotencyKey: true // one Idempotency-Key header, reused on each attempt
});A PATCH becomes retryable with ifMatch or as: 'merge-patch'. To override the safety gate entirely, force it with retry: true; disable per request with retry: false.
When a 429 or 503 carries a Retry-After header, the service waits exactly that long — seconds or an HTTP date — instead of using its own backoff.
await io.get('https://api.example.com/rate-limited', null, {retries: 5});
// a 429 with `Retry-After: 2` pauses 2s before the next attemptIf a DELETE is retried and a later attempt returns 404, the resource is already gone — the service reports 204 No Content rather than surfacing the 404.
const env = await io.full.delete('https://api.example.com/things/1', null, {retries: 3});
env.status; // 204 even if the retry saw a 404initDelay sets the first pause (default 100 ms; use 0 in tests). io.retry.nextDelay(delay, attempt, options) computes each subsequent pause — the default doubles it, capped at 5 s. Replace it for custom backoff or jitter.
await io.get(url, null, {retries: 4, initDelay: 250});
io.retry.initDelay = 200;
io.retry.nextDelay = (delay, attempt) => Math.min(delay * 2, 10000) + Math.random() * 100;Note: a request whose body is a ReadableStream is never retried — the stream cannot be replayed.
Guides
Concepts
Services
Modules
- fetch transport
- keys & URLs
- helpers
- records
- sse
- storage backends
- compression encoders
- Service Worker integration
Cookbook