125

Pseudo code:

$(document).ajaxError(function(e, xhr, options, error) {
  xhr.retry()
})

Even better would be some kind of exponential back-off

Sergio Tulentsev
  • 226,338
  • 43
  • 373
  • 367
Tom Lehman
  • 85,973
  • 71
  • 200
  • 272
  • 1
    I'm not sure if this is the best way at all, so just a comment, but if you call your ajax from a fucntion, you can give it a parameter `tries`, and on fail you call your function with `tries+1`. Stop execution on `tries==3` or any other number. – Nanne Apr 05 '12 at 07:41
  • possible duplicate of [Retry a jquery ajax request which has callbacks attached to its deferred](http://stackoverflow.com/questions/11793430/retry-a-jquery-ajax-request-which-has-callbacks-attached-to-its-deferred) – user2284570 Oct 10 '14 at 13:56
  • Nice solution here: http://stackoverflow.com/questions/6298612/automatically-try-ajax-request-again-on-fail – Jeremy A. West Jun 10 '15 at 17:45

9 Answers9

271

Something like this:


$.ajax({
    url : 'someurl',
    type : 'POST',
    data :  ....,   
    tryCount : 0,
    retryLimit : 3,
    success : function(json) {
        //do something
    },
    error : function(xhr, textStatus, errorThrown ) {
        if (textStatus == 'timeout') {
            this.tryCount++;
            if (this.tryCount <= this.retryLimit) {
                //try again
                $.ajax(this);
                return;
            }            
            return;
        }
        if (xhr.status == 500) {
            //handle error
        } else {
            //handle error
        }
    }
});
Sudhir Bastakoti
  • 99,167
  • 15
  • 158
  • 162
  • 12
    I've taken @Sudhir's solution and created a $.retryAjax plugin on github here: https://github.com/mberkom/jQuery.retryAjax – Mike B Sep 17 '12 at 19:02
  • 3
    This is not working for me. this.tryCount in the conditional is always 1. – user304602 Nov 03 '13 at 10:03
  • 2
    @MichaelBerkompas - does your plugin still work? It has not received commits in 2 years. – Hendrik Dec 05 '14 at 11:03
  • @Hendrik It probably does, but I haven't been using it recently. – Mike B Dec 11 '14 at 17:21
  • 2
    will this work if another callback handler like `.success` is attached to calling the function that returns this ajax request? – ProblemsOfSumit Jan 21 '15 at 12:17
  • 20
    Pair of `tryCount` and `retryLimit` is excessive. Consider using only 1 variable: `this.retryLimit--; if (this.retryLimit) { ... $.ajax(this) ... }` – vladkras Sep 13 '16 at 05:03
  • If I want to retry the request a certain time later. For example, my server gives a 500 error sometimes when I load too many requests, and I want to try it again in say a minute with the same request, what would you recommend as the best way to do that from the structure above? – azoundria Aug 27 '17 at 18:28
  • 1
    Great solution! I used something like that, to change URL for the next try: if(this.tryCount <= this.retryLimit) { // Try again with another URL $.ajax({ url: "https://some.url", dataType: this.dataType, success: this.success }); return; } – Beamer Sep 28 '17 at 15:14
  • 2
    Amazing! I have added additional parameter `timeout : 3000` and then increased the timeout with every retry: `this.timeout = this.timeout * this.tryCount;` perhaps it might give more time for any of the requests to come back with a response. – Duaa Zahi Jul 14 '18 at 18:33
  • What if you'd like to trigger ajax error inside success? – Francisco Ochoa Jul 29 '18 at 21:35
  • @user304602 - Try defining tryCount before $.ajax() `var tryCount = 0` and change every instance of `this.tryCount` to `tryCount` – buycanna.io Sep 01 '18 at 03:50
  • 1
    Wouldn't `this` refer to the error callback function rather than the object passed to `$.ajax()`? – faintsignal Apr 02 '19 at 00:17
  • retryLimit : 3, // 重試次數 retryInterval : 2000, // 重試間隔毫秒數 error : function(xhr, textStatus, errorThrown ) { if (textStatus == 'timeout' || textStatus == 'error' ) { if (this.retryLimit-- > 0) { setTimeout(() => { console.log('try again 開始重試' + this.retryLimit + ', url:' + this.url); $.ajax(this); }, this.retryInterval || 100); return; } return; } – kkasp Oct 18 '22 at 02:24
  • When I try to add tryCount : 0, retryLimit : 3, , I got an error? – Ozmen Jan 19 '23 at 01:58
19

One approach is to use a wrapper function:

(function runAjax(retries, delay){
  delay = delay || 1000;
  $.ajax({
    type        : 'GET',
    url         : '',
    dataType    : 'json',
    contentType : 'application/json'
  })
  .fail(function(){
    console.log(retries); // prrint retry count
    retries > 0 && setTimeout(function(){
        runAjax(--retries);
    },delay);
  })
})(3, 100);

Another approach would be to use a retries property on the $.ajax

// define ajax settings
var ajaxSettings = {
  type        : 'GET',
  url         : '',
  dataType    : 'json',
  contentType : 'application/json',
  retries     : 3  //                 <-----------------------
};

// run initial ajax
$.ajax(ajaxSettings).fail(onFail)

// on fail, retry by creating a new Ajax deferred
function onFail(){
  if( ajaxSettings.retries-- > 0 )
    setTimeout(function(){
        $.ajax(ajaxSettings).fail(onFail);
    }, 1000);
}

Another way (GIST) - override original $.ajax (better for DRY)

// enhance the original "$.ajax" with a retry mechanism 
$.ajax = (($oldAjax) => {
  // on fail, retry by creating a new Ajax deferred
  function check(a,b,c){
    var shouldRetry = b != 'success' && b != 'parsererror';
    if( shouldRetry && --this.retries > 0 )
      setTimeout(() => { $.ajax(this) }, this.retryInterval || 100);
  }

  return settings => $oldAjax(settings).always(check)
})($.ajax);



// now we can use the "retries" property if we need to retry on fail
$.ajax({
    type          : 'GET',
    url           : 'http://www.whatever123.gov',
    timeout       : 2000,
    retries       : 3,     //       <-------- Optional
    retryInterval : 2000   //       <-------- Optional
})
// Problem: "fail" will only be called once, and not for each retry
.fail(()=>{
  console.log('failed') 
});

A point to consider is making sure the $.ajax method wasn't already wrapped previously, in order to avoid the same code running twice.


You can copy-paste these snippets (as-is) to the console to test them

Community
  • 1
  • 1
vsync
  • 118,978
  • 58
  • 307
  • 400
7

I've had a lot of success with this code below (example: http://jsfiddle.net/uZSFK/)

$.ajaxSetup({
    timeout: 3000, 
    retryAfter:7000
});

function func( param ){
    $.ajax( 'http://www.example.com/' )
        .success( function() {
            console.log( 'Ajax request worked' );
        })
        .error(function() {
            console.log( 'Ajax request failed...' );
            setTimeout ( function(){ func( param ) }, $.ajaxSetup().retryAfter );
        });
}
Nabil Kadimi
  • 10,078
  • 2
  • 51
  • 58
  • 4
    The only change I would suggest is to replace 'func("'+param"'")' with function(){func(param)}. That way, you can directly pass the parameter along without converting it to a string and back, which can fail very easily! – fabspro Sep 28 '13 at 15:36
  • 7
    Isn't this an endless loop? Given the question has a retryLimit and is obviously wanting to cater for the server never coming back... I think this really has to be in there – PandaWood May 13 '16 at 05:06
  • 3
    jQuery.ajaxSetup() Description: Set default values for future Ajax requests. Its use is not recommended. http://api.jquery.com/jQuery.ajaxSetup/ – blub Aug 10 '17 at 20:30
3

Your code is almost full :)

const counter = 0;
$(document).ajaxSuccess(function ( event, xhr, settings ) {
    counter = 0;
}).ajaxError(function ( event, jqxhr, settings, thrownError ) {
    if (counter === 0 /*any thing else you want to check ie && jqxhr.status === 401*/) {
        ++counter;
        $.ajax(settings);
    }
});
Andriy
  • 973
  • 8
  • 13
2

None of these answers work if somebody calls .done() after their ajax call because you won't have the success method to attach to the future call back. So if somebody does this:

$.ajax({...someoptions...}).done(mySuccessFunc);

Then mySuccessFunc won't get called on the retry. Here's my solution, which is heavily borrowed from @cjpak's answer here. In my case I want to retry when AWS's API Gateway responds with 502 error.

const RETRY_WAIT = [10 * 1000, 5 * 1000, 2 * 1000];

// This is what tells JQuery to retry $.ajax requests
// Ideas for this borrowed from https://stackoverflow.com/a/12446363/491553
$.ajaxPrefilter(function(opts, originalOpts, jqXHR) {
  if(opts.retryCount === undefined) {
    opts.retryCount = 3;
  }

  // Our own deferred object to handle done/fail callbacks
  let dfd = $.Deferred();

  // If the request works, return normally
  jqXHR.done(dfd.resolve);

  // If the request fails, retry a few times, yet still resolve
  jqXHR.fail((xhr, textStatus, errorThrown) => {
    console.log("Caught error: " + JSON.stringify(xhr) + ", textStatus: " + textStatus + ", errorThrown: " + errorThrown);
    if (xhr && xhr.readyState === 0 && xhr.status === 0 && xhr.statusText === "error") {
      // API Gateway gave up.  Let's retry.
      if (opts.retryCount-- > 0) {
        let retryWait = RETRY_WAIT[opts.retryCount];
        console.log("Retrying after waiting " + retryWait + " ms...");
        setTimeout(() => {
          // Retry with a copied originalOpts with retryCount.
          let newOpts = $.extend({}, originalOpts, {
            retryCount: opts.retryCount
          });
          $.ajax(newOpts).done(dfd.resolve);
        }, retryWait);
      } else {
        alert("Cannot reach the server.  Please check your internet connection and then try again.");
      }
    } else {
      defaultFailFunction(xhr, textStatus, errorThrown); // or you could call dfd.reject if your users call $.ajax().fail()
    }
  });

  // NOW override the jqXHR's promise functions with our deferred
  return dfd.promise(jqXHR);
});

This snippet will back-off and retry after 2 seconds, then 5 seconds, then 10 seconds, which you can edit by modifying the RETRY_WAIT constant.

AWS support suggested we add a retry, since it happens for us only once in a blue moon.

Ryan Shillington
  • 23,006
  • 14
  • 93
  • 108
  • I found this to be the most useful of all the answers so far. However, the last line prevents compilation in TypeScript. I don't think you should be returning anything from this function. – Freddie Mar 31 '20 at 22:31
0

Here is a small plugin for this:

https://github.com/execjosh/jquery-ajax-retry

Auto incrementing timeout would be a good addition to it.

To use it globally just create your own function with $.ajax signature, use there retry api and replace all your $.ajax calls by your new function.

Also you could directly replace $.ajax, but you will not be able to make xhr calls without retry then.

Oleg Isonen
  • 1,463
  • 10
  • 10
0

Here's the method that worked for me for asynchronous loading of libraries:

var jqOnError = function(xhr, textStatus, errorThrown ) {
    if (typeof this.tryCount !== "number") {
      this.tryCount = 1;
    }
    if (textStatus === 'timeout') {
      if (this.tryCount < 3) {  /* hardcoded number */
        this.tryCount++;
        //try again
        $.ajax(this);
        return;
      }
      return;
    }
    if (xhr.status === 500) {
        //handle error
    } else {
        //handle error
    }
};

jQuery.loadScript = function (name, url, callback) {
  if(jQuery[name]){
    callback;
  } else {
    jQuery.ajax({
      name: name,
      url: url,
      dataType: 'script',
      success: callback,
      async: true,
      timeout: 5000, /* hardcoded number (5 sec) */
      error : jqOnError
    });
  }
}

Then just call .load_script from your app and nest your success callback:

$.loadScript('maps', '//maps.google.com/maps/api/js?v=3.23&libraries=geometry&libraries=places&language=&hl=&region=', function(){
    initialize_map();
    loadListeners();
});
Abram
  • 39,950
  • 26
  • 134
  • 184
0

DemoUsers's answer doesn't work with Zepto, since this in the error function is pointing to Window. (And that way of using 'this' is not secure enough as you don't know how they implement ajax or no need to.)

For Zepto, maybe you could try below, till now it works well for me:

var AjaxRetry = function(retryLimit) {
  this.retryLimit = typeof retryLimit === 'number' ? retryLimit : 0;
  this.tryCount = 0;
  this.params = null;
};
AjaxRetry.prototype.request = function(params, errorCallback) {
  this.tryCount = 0;
  var self = this;
  params.error = function(xhr, textStatus, error) {
    if (textStatus === 'timeout') {
      self.tryCount ++;
      if (self.tryCount <= self.retryLimit) {
        $.ajax(self.params)      
        return;
      }
    }
    errorCallback && errorCallback(xhr, textStatus, error);
  };
  this.params = params;
  $.ajax(this.params);
};
//send an ajax request
new AjaxRetry(2).request(params, function(){});

Use constructor to make sure request is reentrant!

Xhua
  • 118
  • 1
  • 7
0

I resolved my specific issue with @vsync 3rd code.

$.ajax = (($oldAjax) => {
    
  var df = $.Deferred();
  
  // on fail, retry by creating a new Ajax deferred
  function check(self, status) {
    console.log("check " + status + " => " + self.retries);
    const shouldRetry = status != 'success' && status != 'parsererror';
    if (shouldRetry && self.retries > 0) {
      setTimeout(() => {
        console.log("retry " + self.retries);
        $.ajax(self);
      }, self.retryInterval || 100);
    }
  }

  function failed(jqXHR, status, e) {
    if (this.retries - 1 <= 0) {
      // 재시도 횟수가 끝나면, 오류 보내기
      df.reject(KfError.convertKfError(jqXHR, this.url));
    } else {
      this.retries --;
      check(this, 'retry', this.retries);
    }
  }

  function done(res, textStatus, jqXHR) {
    if (!res.success) { // 200 코드이지만, 응답에 실패라면 오류로 처리
      if (this.retries - 1 <= 0) {
        df.reject(KfError.createResponseError(res, this.url));
      } else {
        this.retries --;
        check(this, 'retry', this.retries)
      }
    } else {
      df.resolve(res, textStatus, jqXHR);
    }
  }
  return function (settings) {
    $oldAjax(settings)
      .fail(failed)
      .done(done);
    return df;
  };
})($.ajax);

function createRequest(url) {
  return $.ajax({
    type: 'GET',
    url: url,
    timeout: 2000,
    retries: 3,
    retryInterval: 1000
  });
}

$(function () {
  createRequest(Rest.correctUrl('/auth/refres'))
    .then((res) => {
      console.log('ok res');
    })
    .catch((e) => {
      // Finally catch error after retrial.
      console.log(e);
    });
});
Brownsoo Han
  • 4,549
  • 3
  • 20
  • 20