How to implement retry for failed async requests in JavaScript?

Content verified by Anycode AI
July 21, 2024
Probably, one of the most common sources of unpredictable failures in modern web applications are network requests. Temporary failures of servers or network interruptions, or rate limiting may result in request failure at certain times. Such failures, in case they become unmapped, might be the reason for bad user experience and diminished reliability of an application. Graceful Handling of Errors in JavaScript Asynchronous Requests: This tutorial describes how to handle failed asynchronous requests gracefully in JavaScript. We will see how a retry mechanism should work to make sure, after certain failed requests, automatic retries go through as many times as we want. In this way, the chances of success can be improved vastly without human intervention. Apart from improving resilience in applications, it makes the UX better due to the silent recovery from transient failures. A solution like this will also be flexible: It provides for retry attempts by a developer, execution of smart backoff strategies, and certain conditions under which retries should happen. Whether building a frontend application or a Node.js service, this retry implementation can improve the resilience of an application against any issues on the network.

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.

Step-by-Step Guide on Implementing Retry Logic

Define Retry Configuration

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
};

Calculate Delay with Exponential Backoff and Jitter

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;
}

Retry Wrapper Function

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);
  }
}

Async Request Function

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();
}

Using Retry Logic with Async Requests

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);
  }
})();

Complete Example in One Block

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!

Have any questions?
Our CEO and CTO are happy to
answer them personally.
Get Beta Access
Anubis Watal
CTO at Anycode
Alex Hudym
CEO at Anycode