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.