JAVASCRIPT
Robust API Calls with Exponential Backoff and Retries
Implement a resilient JavaScript fetch API wrapper that automatically retries failed requests with exponential backoff, enhancing application stability.
async function fetchWithRetry(url, options = {}, retries = 3, backoff = 300) {
try {
const response = await fetch(url, options);
if (!response.ok) {
// Treat non-OK responses (e.g., 5xx, 4xx) as potential retry candidates,
// but only if it's a server error or a temporary client error (e.g., 429 Too Many Requests)
if (response.status >= 500 || response.status === 429) {
throw new Error(`API error: ${response.status} ${response.statusText}`);
}
// For other client errors (e.g., 400, 401, 403, 404), do not retry, just throw.
// This is a common pattern, adjust based on specific API needs.
console.error(`Non-retriable API error: ${response.status} ${response.statusText}`);
const errorData = await response.json().catch(() => ({ message: response.statusText }));
throw new Error(JSON.stringify({ status: response.status, data: errorData }));
}
return response;
} catch (error) {
if (retries > 0) {
console.warn(`Retrying ${url} in ${backoff}ms due to error: ${error.message}. Retries left: ${retries - 1}`);
await new Promise(resolve => setTimeout(resolve, backoff));
return fetchWithRetry(url, options, retries - 1, backoff * 2); // Exponential backoff
}
throw error;
}
}
// Example usage:
// fetchWithRetry('https://api.example.com/data', { method: 'GET' })
// .then(response => response.json())
// .then(data => console.log('Data fetched:', data))
// .catch(error => console.error('Failed to fetch data after retries:', error.message));
// Simulate a flaky API endpoint
let attemptCount = 0;
const flakyApiUrl = 'https://httpstat.us/503'; // Will return 503 Service Unavailable
// To test a successful retry, uncomment this and comment out the above line:
// const flakyApiUrl = 'https://httpstat.us/200'; // Always succeeds
// setTimeout(() => { flakyApiUrl = 'https://httpstat.us/200'; }, 1000); // Simulate success after delay
const testFetch = async () => {
attemptCount++;
console.log(`Attempt ${attemptCount} for flaky API`);
try {
const response = await fetchWithRetry(flakyApiUrl, {}, 3, 500);
const text = await response.text();
console.log('Flaky API successful:', text);
} catch (error) {
console.error('Flaky API failed after retries:', error.message);
}
};
// Call the test function
// testFetch();
How it works: This `fetchWithRetry` function wraps the native `fetch` API, adding a crucial retry mechanism with exponential backoff. It automatically re-attempts failed requests (e.g., network errors, server errors, or rate limits) after increasing delays, up to a specified number of `retries`. This pattern significantly improves the robustness of your application when interacting with unreliable or transiently unavailable external APIs, ensuring a better user experience by handling temporary issues gracefully instead of failing immediately.