I'm trying to implement a system of retrying ajax requests that fail for a temporary reason. In my case, it is about retrying requests that failed with a 401 status code because the session has expired, after calling a refresh webservice that revives the session.
The problem is that the "done" callbacks are not called on a successful retry, unlike the "success" ajax option callback that is called. I've made up a simple example below:
$.ajaxSetup({statusCode: {
404: function() {
this.url = '/existent_url';
$.ajax(this);
}
}});
$.ajax({
url: '/inexistent_url',
success: function() { alert('success'); }
})
.done(function() {
alert('done');
});
Is there a way to have done-style callbacks called on a successful retry? I know a deferred can't be 'resolved' after it was 'rejected', is it possible to prevent the reject? Or maybe copy the doneList of the original deferred to a new deferred? I'm out of ideas:)
A more realistic example below, where I'm trying to queue up all 401-rejected requests, and retry them after a successful call to /refresh.
var refreshRequest = null,
waitingRequests = null;
var expiredTokenHandler = function(xhr, textStatus, errorThrown) {
//only the first rejected request will fire up the /refresh call
if(!refreshRequest) {
waitingRequests = $.Deferred();
refreshRequest = $.ajax({
url: '/refresh',
success: function(data) {
// session refreshed, good
refreshRequest = null;
waitingRequests.resolve();
},
error: function(data) {
// session can't be saved
waitingRequests.reject();
alert('Your session has expired. Sorry.');
}
});
}
// put the current request into the waiting queue
(function(request) {
waitingRequests.done(function() {
// retry the request
$.ajax(request);
});
})(this);
}
$.ajaxSetup({statusCode: {
401: expiredTokenHandler
}});
The mechanism works, the 401-failed requests get fired a second time, the problem is their 'done' callbacks do not get called, so the applications stalls.
This is a great question that I just faced too.
I was daunted by the accepted answer (from @gnarf), so I figured out a way that I understood easier:
Basically, I just wrapped the entire Ajax call and its callbacks into one function which can get called recursively.
As gnarf's answer notes, success and error callbacks will not behave as expected. If anyone is interested here is a version that supports both
success
anderror
callbacks as well as promises style events.You could use
jQuery.ajaxPrefilter
to wrap the jqXHR in another deferred object.I made an example on
jsFiddle
that shows it working, and tried to adapt some of your code to handle the 401 into this version:This works because
deferred.promise(object)
will actually overwrite all of the "promise methods" on the jqXHR.NOTE: To anyone else finding this, if you are attaching callbacks with
success:
anderror:
in the ajax options, this snippet will not work the way you expect. It assumes that the only callbacks are the ones attached using the.done(callback)
and.fail(callback)
methods of the jqXHR.Would something like this work out for you? You just need to return your own Deferred/Promise so that the original one isn't rejected too soon.
Example/test usage: http://jsfiddle.net/4LT2a/3/
I have created a jQuery plugin for this use case. It wraps the logic described in gnarf's answer in a plugin and additionally allows you to specify a timeout to wait before attempting the ajax call again. For example.