1

I have all my ajax calls in a custom JS file. And trust me there are alot of them! I would like to implement a "retry on fail behavior" in all ajax calls.

Is there a way to do it like "interception"? Or do I have to do it one by one?

My fear here is that a future dev will forget to set the retry policy...

Sample ajax call:

$.ajax({
    url: apiRoot + 'reservationItens?reservaId=' + idReservation + '&bagId=' + idBag,
    type: 'PUT',
    success: function () {
        if (onSuccess != null) {
            onSuccess();
        }
    },
    error: function (x, y, z) {
        if (onError != null) {
            onError(x, y, z);
        }
    }
});
Community
  • 1
  • 1
Leonardo
  • 10,737
  • 10
  • 62
  • 155
  • 1
    some info on why the down-vote/close would be nice... – Leonardo Apr 02 '14 at 20:05
  • Not the downvoter but it would be helpful if you could add an example of how your average ajax call looks like. – Marcel Gwerder Apr 02 '14 at 20:10
  • 1
    Don't use `$.ajax`. Provide your own set of APIs which wrap around it. – user229044 Apr 02 '14 at 20:22
  • 1) onSuccess and onError check fails when variable does not exist. To make it more stable use window.onSuccess and window.onError instead. 2) Program in english so we ALL understand what you try to do. 3) It is not clear EXACTLY what you mean or try to do. – Codebeat Apr 02 '14 at 21:28
  • possible duplicate of [How do I resend a failed ajax request?](http://stackoverflow.com/questions/8881614/how-do-i-resend-a-failed-ajax-request) – user2284570 Oct 10 '14 at 18:59
  • This question seems to be a duplicate of one that has a great answer here: http://stackoverflow.com/a/10024557/470749 – Ryan Mar 01 '17 at 20:33

3 Answers3

6

You can use ajaxError which takes a callback that is called on every ajax error. Additionally you can add a boolean to the settings object and check for it in the callback which ensures that one failed request is only called a second time and not more.

$(document).ajaxError(function (event, jqxhr, settings) {
    if(!settings.secondExec) {
        settings.secondExec = true;
        $.ajax(settings); 
    }
});

If desired add a timeout for the second request to increase the possibility that a random server or connection problem is resolved in the meantime:

setTimeout(function() {
    $.ajax(settings); 
}, 500);

If you want to exclude some requests just add another property to the request settings which you then use like secondExec is used in the example.

Marcel Gwerder
  • 8,353
  • 5
  • 35
  • 60
5

Here's a working jsfiddle.

I'd do it like this, with a recursive function:

function AjaxRetry(settings, maxTries, interval) {
    var self = this;
    this.settings = settings;
    this.maxTries = typeof maxTries === "number" ? maxTries : 0;
    this.completedTries = 0;
    this.interval = typeof interval === "number" ? interval : 0;

    // Return a promise, so that you can chain methods
    // as you would with regular jQuery ajax calls
    return tryAjax().promise();

    function tryAjax(deferred) {
        console.log("Trying ajax #" + (self.completedTries + 1));
        var d = deferred || $.Deferred();
        $.ajax(self.settings)
            .done(function(data) {
                // If it succeeds, don't keep retrying
                d.resolve(data);
            })
            .fail(function(error) {
                self.completedTries++;

                // Recursively call this function again (after a timeout)
                // until either it succeeds or we hit the max number of tries
                if (self.completedTries < self.maxTries) {
                    console.log("Waiting " + interval + "ms before retrying...");
                    setTimeout(function(){
                        tryAjax(d);
                    }, self.interval);
                } else {
                    d.reject(error);
                }
            });
        return d;
    }
}

And then usage is like this:

 var settings = {
     url: "https://httpbin.org/get",
     data: {foo: "bar"},
     contentType: "application/json; charset=UTF-8"
 };
 var maxTries = 3;
 var interval = 500;

 // Make your ajax call and retry up to 3 times,
 // waiting 500 milliseconds between attempts.
 new AjaxRetry(settings, maxTries, interval)
    .done(function(data){
        alert("My ajax call succeeded!");
    })
    .fail(function(error) {
        alert("My ajax call failed :'(");
    })
    .always(function(resp){
        alert("My ajax call is over.");
    });
Carolyn Conway
  • 1,356
  • 1
  • 15
  • 21
  • 1
    This is great! I've been trying to achieve the same thing with a promise race and it was not nearly so clear. You've really helped me by posting this. – Tom Mar 03 '17 at 15:20
  • Happy to help I've thought about adapting this code to handle different error responses (e.g., retry if it's a 503, but stop if it's a 404), but I haven't gotten to it yet. LMK if you come up with any good adaptations! – Carolyn Conway Mar 07 '17 at 23:15
4

You can create api method for ajax calls, just like this one. In the ajaxApi function you can create your own handlers. For example for success or error events, thanks to this developer using this api can attach his handlers, without worrying what else handlers to attach.

function outerSuccesFN() {
    console.log('outerSuccesFN');
}

function outerErroFN() {
    console.log('outerErroFN');
}

function completeFn() {
    console.log(completeFn);
}

function ajaxApi(url, dataType, data, timeout) {

    var ajaxResults = $.ajax({
        url: url,
        dataType: dataType,
        data: data,
        timeout: timeout
    });

    function mySuccesFn() {
        console.log('mySuccesFn');
    }

    function myErroFn() {
        console.log('myErroFn');
    }

    return ajaxResults.done(mySuccesFn).fail(myErroFn);
}

var ajaxResult = ajaxApi('http://api.jquery.com/jsonp/', 'jsonp', {
    title: 'ajax'
}, 15000);

ajaxResult.done(outerSuccesFN).fail(outerErroFN).always(completeFn);
Marcel Gwerder
  • 8,353
  • 5
  • 35
  • 60
Łukasz Szewczak
  • 1,851
  • 1
  • 13
  • 14
  • It might be worthwhile using the export pattern so that you don't clutter the global namespace with methods that only you care about. Please do. – John Dvorak Aug 11 '16 at 18:46