← Back to all snippets
JAVASCRIPT

Graceful API Polling with Exponential Backoff

Implement robust API polling in JavaScript with exponential backoff for retries, a maximum retry limit, and cancellation to efficiently handle asynchronous data updates and prevent excessive requests.

/**
 * Polls an API endpoint with exponential backoff.
 *
 * @param {string} url - The API endpoint to poll.
 * @param {function} conditionFn - A function that returns true when polling should stop (e.g., data is ready).
 * @param {object} options - Polling options.
 * @param {number} options.initialInterval - Initial delay before first retry in ms (default: 1000).
 * @param {number} options.maxInterval - Maximum delay between retries in ms (default: 60000).
 * @param {number} options.maxRetries - Maximum number of retries (default: 10).
 * @param {number} options.multiplier - Multiplier for exponential backoff (default: 2).
 * @param {AbortController} [options.abortController] - Optional AbortController for external cancellation.
 * @returns {Promise<any>} The final data when conditionFn is met or after max retries.
 */
async function pollWithExponentialBackoff(url, conditionFn, options = {}) {
  const {
    initialInterval = 1000,
    maxInterval = 60000,
    maxRetries = 10,
    multiplier = 2,
    abortController
  } = options;

  let currentInterval = initialInterval;
  let retries = 0;

  while (retries < maxRetries) {
    if (abortController && abortController.signal.aborted) {
      throw new Error('Polling aborted by AbortController.');
    }

    try {
      const response = await fetch(url, { signal: abortController?.signal });
      if (!response.ok) {
        throw new Error(`HTTP error! Status: ${response.status}`);
      }
      const data = await response.json();

      if (conditionFn(data)) {
        console.log('Polling succeeded: Condition met!');
        return data;
      } else {
        console.log(`Polling ${url}: Condition not met. Retrying in ${currentInterval}ms...`);
      }
    } catch (error) {
      if (error.name === 'AbortError') {
        console.log('Polling aborted due to network error or external cancellation.');
        throw error;
      }
      console.error(`Polling ${url} failed on attempt ${retries + 1}:`, error.message);
    }

    retries++;
    // Wait using a promise that resolves after currentInterval
    await new Promise(resolve => setTimeout(resolve, currentInterval));

    // Increase interval for next retry, capping at maxInterval
    currentInterval = Math.min(currentInterval * multiplier, maxInterval);
  }

  throw new Error(`Polling failed after ${maxRetries} retries. Condition not met.`);
}

// Example Usage:
// (async () => {
//   const mockApi = {
//     requests: 0,
//     getData: function() {
//       this.requests++;
//       if (this.requests < 3) { // Simulate data not ready for first 2 requests
//         console.log('Mock API: Data not ready yet.');
//         return { status: 'pending', value: null };
//       }
//       console.log('Mock API: Data is ready!');
//       return { status: 'complete', value: 'Some important data!' };
//     }
//   };

//   const abortController = new AbortController();

//   try {
//     // Simulate polling a backend endpoint that returns 'pending' until ready
//     const result = await pollWithExponentialBackoff(
//       'https://mock.api/some-task-status', // A dummy URL, actual fetch is mocked
//       (data) => data.status === 'complete',
//       {
//         initialInterval: 500, // Start with 0.5s delay
//         maxInterval: 5000,    // Max 5s delay
//         maxRetries: 5,
//         abortController: abortController
//       }
//     );
//     console.log('Final Polling Result:', result);

//     // Example of external cancellation:
//     // setTimeout(() => {
//     //   abortController.abort();
//     //   console.log('Externally aborted polling after 2 seconds.');
//     // }, 2000);

//   } catch (error) {
//     console.error('Polling process failed:', error.message);
//   }
// })();
How it works: This JavaScript snippet provides a robust `pollWithExponentialBackoff` function for gracefully handling API polling. It repeatedly fetches data from a specified URL until a `conditionFn` returns true (indicating the data is ready), a maximum number of retries (`maxRetries`) is reached, or an external `AbortController` signals cancellation. Crucially, it employs **exponential backoff**, increasing the delay between retries (`currentInterval`) exponentially up to a `maxInterval`. This prevents hammering the API with requests, especially during temporary failures or when waiting for long-running processes, making the polling mechanism more resilient and network-friendly. The integration with `AbortController` allows for clean external cancellation, improving user experience and resource management.

Need help integrating this into your project?

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

Hire DigitalCodeLabs