Making sure our applications can handle hiccups during async requests is super important. You don’t want a simple network glitch to bring everything down, right? So let’s talk about how to add some retry logic to our async requests in JavaScript using a step-by-step approach.
First things first, we need a config object, like our recipe's ingredients. It includes the maximum retries, the initial delay, and an exponential backoff factor. Adding a bit of jitter helps avoid the thundering herd issue where multiple retries happen at the same time.
const retryConfig = {
maxRetries: 5,
initialDelay: 1000, // initial delay in milliseconds
backoffFactor: 2, // exponential backoff factor
jitter: 300, // max jitter in milliseconds
};
Next, we’ll calculate the delay between retries. This uses exponential backoff, which means the wait time increases exponentially, plus a bit of random jitter.
function calculateDelay(attempt, initialDelay, backoffFactor, jitter) {
const exponentialDelay = initialDelay * Math.pow(backoffFactor, attempt);
const randomJitter = Math.random() * jitter;
return exponentialDelay + randomJitter;
}
This is where the magic happens. Create a function retryRequest
that tries the request, catches errors, and retries if needed.
async function retryRequest(requestFn, config, attempt = 0) {
try {
// Try the async request
return await requestFn();
} catch (error) {
// If we've exhausted retries, throw the error
if (attempt >= config.maxRetries) {
throw error;
}
// Calculate delay before next retry
const delay = calculateDelay(attempt, config.initialDelay, config.backoffFactor, config.jitter);
console.log(`Retrying request in ${delay}ms... (Attempt ${attempt + 1})`);
// Wait before retrying
await new Promise(resolve => setTimeout(resolve, delay));
// Retry the request with incremented attempt
return retryRequest(requestFn, config, attempt + 1);
}
}
Let's create or use a function that performs the async request. Here, we’ll use fetch
to make an HTTP request.
async function exampleRequest(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
}
Finally, wrap your async request function with the retryRequest
function. Pass in the request function and your retry configuration.
(async () => {
const url = 'https://jsonplaceholder.typicode.com/posts/1';
try {
const data = await retryRequest(() => exampleRequest(url), retryConfig);
console.log('Data received:', data);
} catch (error) {
console.error('Failed to fetch data after multiple retries:', error);
}
})();
For convenience, here’s the complete code in one chunk:
const retryConfig = {
maxRetries: 5,
initialDelay: 1000, // initial delay in milliseconds
backoffFactor: 2, // exponential backoff factor
jitter: 300, // max jitter in milliseconds
};
function calculateDelay(attempt, initialDelay, backoffFactor, jitter) {
const exponentialDelay = initialDelay * Math.pow(backoffFactor, attempt);
const randomJitter = Math.random() * jitter;
return exponentialDelay + randomJitter;
}
async function retryRequest(requestFn, config, attempt = 0) {
try {
// Try the async request
return await requestFn();
} catch (error) {
// If we've exhausted retries, throw the error
if (attempt >= config.maxRetries) {
throw error;
}
// Calculate delay before next retry
const delay = calculateDelay(attempt, config.initialDelay, config.backoffFactor, config.jitter);
console.log(`Retrying request in ${delay}ms... (Attempt ${attempt + 1})`);
// Wait before retrying
await new Promise(resolve => setTimeout(resolve, delay));
// Retry the request with incremented attempt
return retryRequest(requestFn, config, attempt + 1);
}
}
async function exampleRequest(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
}
(async () => {
const url = 'https://jsonplaceholder.typicode.com/posts/1';
try {
const data = await retryRequest(() => exampleRequest(url), retryConfig);
console.log('Data received:', data);
} catch (error) {
console.error('Failed to fetch data after multiple retries:', error);
}
})();
And there you have it! This retry mechanism with exponential backoff and jitter ensures that if an async request fails, it'll be gracefully retried before admitting defeat. Happy coding!