Axios: Adding Retry Parameter

Created on 27 Nov 2015  ·  23Comments  ·  Source: axios/axios

I have one API that time to time returns ECONNRESET. So I'd like to do retry on such an error. Do you have plans to add retry feature to this library?

Most helpful comment

@mericsson I am too in need of exponential backoff when retrying. I've put together the following which works great. You my want to add in checking for specific errors/status codes as this currently just intercepts and retries all errors.

axios.interceptors.response.use(undefined, function axiosRetryInterceptor(err) {
    var config = err.config;
    // If config does not exist or the retry option is not set, reject
    if(!config || !config.retry) return Promise.reject(err);

    // Set the variable for keeping track of the retry count
    config.__retryCount = config.__retryCount || 0;

    // Check if we've maxed out the total number of retries
    if(config.__retryCount >= config.retry) {
        // Reject with the error
        return Promise.reject(err);
    }

    // Increase the retry count
    config.__retryCount += 1;

    // Create new promise to handle exponential backoff
    var backoff = new Promise(function(resolve) {
        setTimeout(function() {
            resolve();
        }, config.retryDelay || 1);
    });

    // Return the promise in which recalls axios to retry the request
    return backoff.then(function() {
        return axios(config);
    });
});

To use:

axios.get('/some/endpoint', { retry: 5, retryDelay: 1000 })
    .then(function(res) {
        console.log('success', res.data);
    })
    .catch(function(err) {
        console.log('failed', err);
    });

Config Options:
retry - Number of times to retry the request after first failed request.
retryDelay - Number of milliseconds to wait in between failed requests (defaults to 1).

I may make this a bit more configurable in a gist at some point.

All 23 comments

any update on this?

I think this is very important. The scenario maybe when we implement Axios for mobile web, then the user switch the internet source from WiFi to any kind of mobile connection.

I don't think this kind of functionality should be in this library. If you need this, you can create a interceptor for this.

@jtangelder any ideas how should it look like?

function retryFailedRequest (err) {
  if (err.status === 500 && err.config && !err.config.__isRetryRequest) {
    err.config.__isRetryRequest = true;
    return axios(err.config);
  }
  throw err;
}
axios.interceptors.response.use(undefined, retryFailedRequest);

Something like this! It retries 500 errors once.

I agree with @jtangelder that this doesn't belong in the core library. This is a great use case for interceptors as illustrated.

@jtangelder @mzabriskie thanks! actually it is great example for documentation too

+1 I wish this example was easier to find!

@jtangelder thanks a lot for the snippet

A little correction on the snippet, I think the returned error status should be 504 instead of 500.

@jtangelder very helpful example.

I'm wondering, any ideas on how to do something with exponential backoff? Is it possible to return a promise from retryFailedRequest? Or other suggestions? Thanks!

The example in @jtangelder should be included in the cookbook page

You can now use axios-retry for this.

I agree. This should really be a part of the library, or at least better-documented.

@mericsson I am too in need of exponential backoff when retrying. I've put together the following which works great. You my want to add in checking for specific errors/status codes as this currently just intercepts and retries all errors.

axios.interceptors.response.use(undefined, function axiosRetryInterceptor(err) {
    var config = err.config;
    // If config does not exist or the retry option is not set, reject
    if(!config || !config.retry) return Promise.reject(err);

    // Set the variable for keeping track of the retry count
    config.__retryCount = config.__retryCount || 0;

    // Check if we've maxed out the total number of retries
    if(config.__retryCount >= config.retry) {
        // Reject with the error
        return Promise.reject(err);
    }

    // Increase the retry count
    config.__retryCount += 1;

    // Create new promise to handle exponential backoff
    var backoff = new Promise(function(resolve) {
        setTimeout(function() {
            resolve();
        }, config.retryDelay || 1);
    });

    // Return the promise in which recalls axios to retry the request
    return backoff.then(function() {
        return axios(config);
    });
});

To use:

axios.get('/some/endpoint', { retry: 5, retryDelay: 1000 })
    .then(function(res) {
        console.log('success', res.data);
    })
    .catch(function(err) {
        console.log('failed', err);
    });

Config Options:
retry - Number of times to retry the request after first failed request.
retryDelay - Number of milliseconds to wait in between failed requests (defaults to 1).

I may make this a bit more configurable in a gist at some point.

@KyleRoss Thanks for the code, I made an adjustment for the retries to be exponential

// Create new promise to handle exponential backoff. formula (2^c - 1 / 2) * 1000(for mS to seconds)
    const backOffDelay = config.retryDelay 
        ? ( (1/2) * (Math.pow(2, config.__retryCount) - 1) ) * 1000
        : 1;

    const backoff = new Promise((resolve) => {
        setTimeout(() => {
            resolve();
        },  backOffDelay);
    });

@KyleRoss Thanks for sharing.

The delay can be set increasingly:

const RETRY_TIMEOUTS = [1, 3, 5, 10]; // seconds
const delay = RETRY_TIMEOUTS[config.retryCount] * 1000;

If you want to retry an 4xx or 5xx request, you need work with the error object like that:

To intercept your requests:

axios.interceptors.request.use(function (config) {
    // Do something before request is sent
    return config;
  }, function (error) {
    // Do something with request error
    return Promise.reject(error);
  });

To intercept your responses:

axios.interceptors.response.use(function (response) {
    // Do something with response data
    return response;
  }, function (error) {
    // Do something with response error
    return Promise.reject(error);
  });

Awesome ideas. I rolled a fair bit of this up into an npm module, if folks are interested:
https://github.com/JustinBeckwith/retry-axios

Happy to take feature requests ✨

axios-retry vs retry-axios

@KyleRoss I tried adding the retry and retryDelay configuration options to AxiosRequestConfig but it doesnt look like they exist. How did you get that to work?

@lawloretienne you need you make sure you add the code in my example prior to using any of the retry parameters. Since it acts more like a plugin, the new properties are not documented as part of axios itself.

config.__retryCount = config.__retryCount || 0;

@KyleRoss Unable to pass config config, each 'config.__reyCount' is undefined.
https://github.com/axios/axios/blob/v0.19.0/lib/core/mergeConfig.js
Custom properties are now filtered out.

@dennisreimann custom config are now filtered out in v0.19, and now we can pass config like this:

axios.defaults.headers.common['retry'] = 3
axios.defaults.headers.common['retryDelay'] = 1000
axios.defaults.headers.common['retryCount'] = 0

@mericsson I am too in need of exponential backoff when retrying. I've put together the following which works great. You my want to add in checking for specific errors/status codes as this currently just intercepts and retries all errors.

axios.interceptors.response.use(undefined, function axiosRetryInterceptor(err) {
    var config = err.config;
    // If config does not exist or the retry option is not set, reject
    if(!config || !config.retry) return Promise.reject(err);

    // Set the variable for keeping track of the retry count
    config.__retryCount = config.__retryCount || 0;

    // Check if we've maxed out the total number of retries
    if(config.__retryCount >= config.retry) {
        // Reject with the error
        return Promise.reject(err);
    }

    // Increase the retry count
    config.__retryCount += 1;

    // Create new promise to handle exponential backoff
    var backoff = new Promise(function(resolve) {
        setTimeout(function() {
            resolve();
        }, config.retryDelay || 1);
    });

    // Return the promise in which recalls axios to retry the request
    return backoff.then(function() {
        return axios(config);
    });
});

To use:

axios.get('/some/endpoint', { retry: 5, retryDelay: 1000 })
    .then(function(res) {
        console.log('success', res.data);
    })
    .catch(function(err) {
        console.log('failed', err);
    });

Config Options:
retry - Number of times to retry the request after first failed request.
retryDelay - Number of milliseconds to wait in between failed requests (defaults to 1).

I may make this a bit more configurable in a gist at some point.

thanks for help!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

altruisticsoftware picture altruisticsoftware  ·  3Comments

ildella picture ildella  ·  3Comments

varmeh picture varmeh  ·  3Comments

Shu-Ji picture Shu-Ji  ·  3Comments

tbaustin picture tbaustin  ·  3Comments