Fetch API Retry with Exponential Backoff in JavaScript
Learn how to write a resilient Fetch API wrapper in JavaScript featuring automated retries and exponential backoff delays. Complete copy-paste utility script.
Official Documentation Reference
For in-depth explanations, configuration options, and standards compliance, visit the official documentation:
MDN Web Docs - Fetch API →Ready to test this code?
Code Example
async function fetchWithRetry(url, options = {}, retries = 3, backoffMs = 1000) {
try {
const response = await fetch(url, options);
if (!response.ok) {
const status = response.status;
const shouldRetry = status === 408 || status === 429 || (status >= 500 && status <= 599);
if (shouldRetry && retries > 0) {
await new Promise((resolve) => setTimeout(resolve, backoffMs));
return fetchWithRetry(url, options, retries - 1, backoffMs * 2);
}
throw new Error(`HTTP Error: ${status}`);
}
return response;
} catch (error) {
if (retries > 0) {
await new Promise((resolve) => setTimeout(resolve, backoffMs));
return fetchWithRetry(url, options, retries - 1, backoffMs * 2);
}
throw error;
}
}Overview
Fetch API Retry with Exponential Backoff in JavaScript
Modern web applications rely heavily on HTTP API connections. However, networks are inherently unreliable. Temporary service dropouts, rate limiting (HTTP 429), or server-side restarts (HTTP 502/503/504) can cause API requests to fail. To make your application resilient, you should implement a client-side retry mechanism with exponential backoff.
Exponential backoff is a strategy where you progressively increase the delay between consecutive retry attempts, helping prevent overloading your server during traffic spikes.
Implementation Code
Here is a complete, lightweight, and framework-agnostic utility function in JavaScript using modern async/await syntax:
async function fetchWithRetry(url, options = {}, retries = 3, backoffMs = 1000) { try { const response = await fetch(url, options); // Do not retry client errors (400-408) unless it is a timeout (408) or rate limit (429) if (!response.ok) { const status = response.status; const shouldRetry = status === 408 || status === 429 || (status >= 500 && status <= 599); if (shouldRetry && retries > 0) { console.warn(`Request failed with status ${status}. Retrying in ${backoffMs}ms... (${retries} attempts left)`); await new Promise((resolve) => setTimeout(resolve, backoffMs)); return fetchWithRetry(url, options, retries - 1, backoffMs * 2); // Exponential backoff double delay } throw new Error(`HTTP Error: ${status} ${response.statusText}`); } return response; } catch (error) { // Network errors (e.g., DNS failure, server offline) throw errors in fetch if (retries > 0) { console.warn(`Network error encountered: ${error.message}. Retrying in ${backoffMs}ms...`); await new Promise((resolve) => setTimeout(resolve, backoffMs)); return fetchWithRetry(url, options, retries - 1, backoffMs * 2); } throw error; } }
How It Works
- Initial Call: The function attempts a standard
fetchcall. - Status Check: If the response is not
ok, it checks the HTTP status code. It will only schedule retries for transient errors:- 408 Request Timeout
- 429 Too Many Requests (Rate limiting)
- 5xx Server Errors (500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout)
- Exponential Delay Increase: During each retry, the delay increases exponentially (
backoffMs * 2). If the initial delay is 1,000ms, the next attempt waits 2,000ms, followed by 4,000ms. - Fallback/Throw: If all retry attempts are exhausted, the function throws the final error, allowing your UI to handle the failure gracefully.
Adding Jitter (Advanced Optimization)
In high-concurrency systems, if many clients encounter an error simultaneously, they will all retry at exactly the same time. This is called the "thundering herd" problem. To prevent this, add randomized jitter to spread out client requests:
const randomizedBackoff = backoffMs * 2 + (Math.random() * 200 - 100); // Add +/- 100ms jitter
References
Frequently Asked Questions
Q: Does fetch throw errors on 500 status codes?
A: No. The browser's native fetch only throws a TypeError if a network connection fails entirely or if CORS is misconfigured. Server response errors (like 4xx/5xx) return successfully with response.ok = false. You must manually throw an error as done in the code above.
Q: Should I retry POST requests? A: Generally, you should only retry safe/idempotent requests (like GET, HEAD, PUT, and DELETE). Retrying a failed POST request could cause duplicate transactions or create duplicate resources on the server if the request actually went through but the connection dropped before returning the response.

