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.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs