66

I would like to retry 5xx requests using axios. I have my main request in the middle of a try catch block. I am using the axios-retry library to auto retry 3 times.

The url i am using will deliberately throw a 503. However the request is not being retried, instead it is being caught in my catch block.

axiosRetry(axios, {
  retries: 3
});

let result;

const url = "https://httpstat.us/503";
const requestOptions = {
  url,
  method: "get",
  headers: {
  },
  data: {},
};


try {

  result = await axios(requestOptions);

} catch (err) {
  throw new Error("Failed to retry")
}

}
return result;
Kay
  • 17,906
  • 63
  • 162
  • 270

9 Answers9

97

axios-retry uses axios interceptor to retry HTTP requests. It intercepts requests or responses before they are handled by then or catch. Below is the working code snippet.

const axios = require('axios');
const axiosRetry = require('axios-retry');

axiosRetry(axios, {
    retries: 3, // number of retries
    retryDelay: (retryCount) => {
        console.log(`retry attempt: ${retryCount}`);
        return retryCount * 2000; // time interval between retries
    },
    retryCondition: (error) => {
        // if retry condition is not specified, by default idempotent requests are retried
        return error.response.status === 503;
    },
});
 
async function makeHTTPCall() {
    const response = await axios({
        method: 'GET',
        url: 'https://httpstat.us/503',
    }).catch((err) => {
        if (err.response.status !== 200) {
            throw new Error(`API call failed with status code: ${err.response.status} after 3 retry attempts`);
        }
    });
}

makeHTTPCall();
ns15
  • 5,604
  • 47
  • 51
32

use retry

const retry = require('retry');

const operation = retry.operation({
  retries: 5,
  factor: 3,
  minTimeout: 1 * 1000,
  maxTimeout: 60 * 1000,
  randomize: true,
});

operation.attempt(async (currentAttempt) => {
  console.log('sending request: ', currentAttempt, ' attempt');
  try {

    await axios.put(...);

  } catch (e) {
    if (operation.retry(e)) { return; }
  }
});
Junius L
  • 15,881
  • 6
  • 52
  • 96
  • 3
    Finally an example that actually works - thank you! – NSchorr Jul 11 '20 at 08:46
  • @user:3858806 This returns a promise. What changes would we need for : a) when the call is success + we want the response b) when the call is success + we need no response i.e. void – Arnab Dutta Jan 05 '22 at 02:25
  • Why can't you just ignore the response? – pip Jun 02 '22 at 14:42
  • 1
    This is the best answer. It allows retrying any operation generically, including Axios API calls. This example is actually more clear than the `retry` documentation itself! Thanks – TetraDev Jul 28 '22 at 18:26
16

You could use axios.interceptors.response.use to do that. When you encounter the error, you could recursive return axios request with resolve. Then you could defined the condition by youself whenever you want to retry. This is a simple axios retry version, and this is also a concept which axios-retry do.

const axios = require("axios")
/**
 * 
 * @param {import("axios").AxiosInstance} axios 
 * @param {Object} options 
 * @param {number} options.retry_time
 * @param {number} options.retry_status_code
 */
const retryWrapper = (axios, options) => {
    const max_time = options.retry_time;
    const retry_status_code = options.retry_status_code;
    let counter = 0;
    axios.interceptors.response.use(null, (error) => {
        /** @type {import("axios").AxiosRequestConfig} */
        const config = error.config
        // you could defined status you want to retry, such as 503
        // if (counter < max_time && error.response.status === retry_status_code) {
        if (counter < max_time) {
            counter++
            return new Promise((resolve) => {
                resolve(axios(config))
            })
        }
        return Promise.reject(error)
    })
}

async function main () {
    retryWrapper(axios, {retry_time: 3})
    const result = await axios.get("https://api.ipify.org?format=json")
    console.log(result.data);
}

main()

I also make a demo version to simulate the final request is success, but previous request all failed. I defined the status_code: 404 I want to retry, and setup 3 times for retry.

const axios = require("axios")
/**
 * 
 * @param {import("axios").AxiosInstance} axios 
 * @param {Object} options 
 * @param {number} options.retry_time
 * @param {number} options.retry_status_code
 */
const retryWrapper = (axios, options) => {
    const max_time = options.retry_time;
    const retry_status_code = options.retry_status_code;
    let counter = 0;
    axios.interceptors.response.use(null, (error) => {
        console.log("==================");
        console.log(`Counter: ${counter}`);
        console.log("Error: ", error.response.statusText);
        console.log("==================");

        /** @type {import("axios").AxiosRequestConfig} */
        const config = error.config
        if (counter < max_time && error.response.status === retry_status_code) {
            counter++
            return new Promise((resolve) => {
                resolve(axios(config))
            })
        }
        // ===== this is mock final one is a successful request, you could delete one in usage.
        if (counter === max_time && error.response.status === retry_status_code) {
            config.url = "https://api.ipify.org?format=json"
            return new Promise((resolve) => {
                resolve(axios(config))
            })
        }
        return Promise.reject(error)
    })
}

async function main () {
    retryWrapper(axios, {retry_time: 3, retry_status_code: 404})
    const result = await axios.get("http://google.com/not_exist")
    console.log(result.data);
}

main()

You will see the log print following message.

==================
Counter: 0
Error:  Not Found
==================
==================
Counter: 1
Error:  Not Found
==================
==================
Counter: 2
Error:  Not Found
==================
==================
Counter: 3
Error:  Not Found
==================
{ ip: 'x.x.x.x' }
Jack Yu
  • 2,190
  • 1
  • 8
  • 14
  • One way we could do it is check if the response status code is not equal to 200 (ie error.response.status) as the errors could ber varying like 400,401,404 etc. but that would also depend on the use case. – Anshuman Kumar Jul 02 '22 at 13:15
  • this worked for me. Now.. how do I add a delay? Combining your answer with the other stackoverflow answers on this topic does not work :) – john k Jan 10 '23 at 19:17
  • @johnktejik you could use setTimeout + Promise to do delay. Check this https://jsfiddle.net/fvLtyj4w/1/. – Jack Yu Jan 11 '23 at 15:49
  • This is great, thanks! Q: since axios itself returns promises, why do we need to do that ourselves. Why `return new Promise((resolve) => { resolve(axios(config)) })`? Doesn't that return a promise wrapped in a promise? Why not just `return axios(config)`? – XML Aug 15 '23 at 19:12
10

You can use axios interceptors to intercept the response and retry the request.

You can intercept requests or responses before they are handled by then or catch.

See axios interceptors


There are 2 popular packages that already leverage axios interceptors to do just that:

Here's a NPM compare link to help you decide between the two

ndraiman
  • 663
  • 1
  • 10
  • 18
9

Updating Andrien answer to prevent process leak. We should stop the loop once the request succeeded and also forgot to put increment so it means it never stop the loop. This will only retry if the request failed with the maximum of 3. You can increase it by changing maxRetries based on your preferred value.

new Promise(async (resolve, reject) => {
  let retries = 0;
  let success = false;
  const maxRetries = 3;

  while (retries < maxRetries && !success) {
    try {
      const response = await axios(options);
      success = true
      resolve(response.data);
    } catch (err) {
      const status = err?.response?.status || 500;
      console.log(`Error Status: ${status}`);
    }
    retries++
  }
  console.log(`Too many request retries.`);
  reject()
})

For more details check this documention of while loop: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/while

Tallboy
  • 12,847
  • 13
  • 82
  • 173
John Aldrin
  • 99
  • 1
  • 4
  • Hi, i tried your solution and i keepe getting Unexpected unhandledRejection event for promise which rejection does get handled – kd12345 Jan 22 '23 at 07:18
4

The accepted solution will force all failed HTTP requests (using axios) to retry. This is defentely not what any one want by default.

A solution would be to create a new instance of axios for a specific HTTP call and define the retry strategy to it:

import axios from 'axios'
import axiosRetry from 'axios-retry'


async function call(){
  const client = axios.create()
  axiosRetry(client, { retries: 4, /* ...more options */ })
  
  const response = client.get("https://www.google.com")
}

call()

``
Stav Alfi
  • 13,139
  • 23
  • 99
  • 171
2

This uses the retry library that Junius L. referenced, but I wrapped it in a promise. It'll reject when it times out and resolve once it's successful.

const retry = require('retry');
const sendWithRetry = axiosCall => new Promise((resolve, reject) => {
    const operation = retry.operation({
        retries: 5,
        factor: 3,
        minTimeout: 1 * 1000,
        maxTimeout: 60 * 1000,
        randomize: true,
    });
    operation.attempt(async (currentAttemptNumber) => {
        try {
            await axiosCall;
        } catch (e) {
            if (operation.retry(e)) { return; }
            resolve();
        }
    }, reject);
});
sissonb
  • 3,730
  • 4
  • 27
  • 54
0

You can do this by not using another library.

new Promise(async (resolve, reject) => {
  let retries = 0;
  const maxRetries = 3;

  while (retries < maxRetries) {
    try {
      const response = await axios(options);
      break;
      resolve(response.data);
    } catch (err) {
      const status = err?.response?.status || 500;
      console.log(`Error Status: ${status}`);
      retries++
    }
  }
  console.log(`Too many request retries.`);
  reject()
})   
Andrien Pecson
  • 282
  • 3
  • 13
0

I had no idea what to look for, then this stackoverflow post and I'll say I took a little from everyone, so thank you. Here's my take

axios.interceptors.response.use(
        response => {


            return response;

        },
        async error => {

            // @link https://stackoverflow.com/questions/56074531/how-to-retry-5xx-requests-using-axios/75956421#75956421
            if (error?.config?.headers?.['X-Retry-Count'] !== undefined) {
                console.log('X-Retry-Count', error?.config?.headers?.['X-Retry-Count'])
                if (false === isTest || true === isVerbose) {
                    console.log(error)
                }
                return error;
            }

            logResponseSetCookiesForTests(error);

            error.response ??= {};

            error.response.status ??= 520;

            const shouldRetry = (error) => undefined !== error.config && error?.response?.status >= 500 && error?.response?.status < 600

            const firstRetry = shouldRetry(error)

            if (false === isTest || true === isVerbose) {

                console.group("Retrying request ", error.config?.url ?? error.config);
                console.log(error);
                console.groupEnd();

            } else if (isTest) {

                console.log('AXIOS ERROR', error.code, error.baseURL, error.config?.url, error.headers, error.data, error.params, error.path, error.response?.status, error.response?.statusText, error.response?.headers, error.response?.data)

                if (false === firstRetry) {
                    throw new Error(error.code + ' ' + error.config?.url)
                }

            }

            if (false === firstRetry) {

                console.error("Error in axiosInterceptors.tsx (Not attempting retry)", error);

                if (undefined !== error?.response?.data?.TRACE ||
                    undefined === error?.response?.data?.alert) {

                    console.log('backend throwable', error?.response?.data || error?.response)

                    if (undefined !== error?.response?.data
                        && Array.isArray(error.response.data)) {

                        error.response.data.status = error?.response?.status

                    }

                    return Promise.reject(error);

                }

                /* Do something with response error
                   this changes from project to project depending on how your server uses response codes.
                   when you can control all errors universally from a single api, return Promise.reject(error);
                   is the way to go.
                */
                HandleResponseCodes(error.response);


                return Promise.reject(error);

            }

            console.warn("Error in axiosInterceptors.tsx - Attempting retry!!!");

            const config: AxiosRequestConfig = error.config

            // @link https://stackoverflow.com/questions/3561381/custom-http-headers-naming-conventions
            let retries = parseInt(config.headers?.['X-Retry-Count'] ?? '0');

            const maxRetries = 3;

            // todo - handle retries better
            while (retries < maxRetries) {

                config.headers = {
                    ...config.headers,
                    'X-Retry-Count': `${++retries}`
                }

                try {

                    // @link https://stackoverflow.com/questions/51563821/axios-interceptors-retry-original-request-and-access-original-promise
                    return axios(config)

                } catch (err) {

                    error = err;

                    return Promise.reject(error);

                }

            }

            console.log(`Too many request retries.`);

            return Promise.reject(error);

        });