← Back to all snippets
JAVASCRIPT

Implementing Client-Side API Rate Limit Handling with Retry-After Header

Learn how to gracefully handle API rate limits on the client-side by parsing the `Retry-After` header and implementing a delayed retry mechanism.

async function fetchWithRateLimitHandling(url, options = {}, retries = 3) {
  let currentRetries = 0;

  while (currentRetries <= retries) {
    try {
      const response = await fetch(url, options);

      if (response.status === 429) { // Too Many Requests
        const retryAfterHeader = response.headers.get('Retry-After');
        let delay = 1; // Default delay in seconds

        if (retryAfterHeader) {
          if (!isNaN(retryAfterHeader)) {
            delay = parseInt(retryAfterHeader, 10);
          } else {
            // Handle HTTP-date format, e.g., "Retry-After: Fri, 31 Dec 1999 23:59:59 GMT"
            const retryDate = new Date(retryAfterHeader);
            const now = new Date();
            if (retryDate > now) {
              delay = Math.ceil((retryDate.getTime() - now.getTime()) / 1000); // Convert ms to s
            }
          }
        }

        console.warn(`Rate limit hit. Retrying after ${delay} seconds. Attempt ${currentRetries + 1} of ${retries + 1}.`);
        await new Promise(resolve => setTimeout(resolve, delay * 1000));
        currentRetries++;
        continue; // Retry the request
      }

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      return await response.json();

    } catch (error) {
      console.error("Error during fetch operation:", error);
      // If it's not a 429 error or we've run out of retries, re-throw
      if (error.message && error.message.includes('429') && currentRetries < retries) {
          currentRetries++;
          // For non-429 errors, we might want to just break or rethrow immediately
          // For example, network error won't have response.status
          console.warn(`Non-429 related error. Retrying attempt ${currentRetries} of ${retries}. Or breaking.`);
          // For simplicity, this example only retries on 429. For other errors, you might want a different retry strategy.
          await new Promise(resolve => setTimeout(resolve, 2000)); // Default small delay for other errors if retrying
          continue;
      } else {
          throw error;
      }
    }
  }
  throw new Error(`Failed to fetch after ${retries + 1} attempts due to rate limiting or other errors.`);
}

// Example usage:
// fetchWithRateLimitHandling('https://api.example.com/protected-endpoint', { method: 'GET' }, 2)
//   .then(data => console.log('Successfully fetched:', data))
//   .catch(error => console.error('Final fetch failed:', error));
How it works: This JavaScript function demonstrates a robust client-side approach to handling API rate limits. It listens for a `429 Too Many Requests` status code and parses the `Retry-After` HTTP header to determine how long to wait before retrying the request. It includes a retry mechanism with a defined number of attempts, ensuring a smoother user experience even when facing temporary API constraints. This pattern helps prevent excessive API calls and makes your application more resilient.

Need help integrating this into your project?

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

Hire DigitalCodeLabs