重试一个jQuery的Ajax请求的callback附加到其延期

我试图实现一个系统重试ajax请求失败,暂时的原因。 在我的情况下,它是关于重试401状态代码失败的请求,因为会话已经过期,在调用刷新Web服务,恢复会话之后。

问题是,“成功”的callback不会被称为成功的重试,不像被称为“成功”的ajax选项callback。 我已经在下面做了一个简单的例子:

$.ajaxSetup({statusCode: { 404: function() { this.url = '/existent_url'; $.ajax(this); } }}); $.ajax({ url: '/inexistent_url', success: function() { alert('success'); } }) .done(function() { alert('done'); }); 

有没有办法做成风格的callback调用成功的重试? 我知道推迟不能被“解决”后,被拒绝,是否有可能防止拒绝? 或者,也许复制原来推迟到一个新的延期? 我没有想法:)

下面是一个更现实的例子,我试图排队所有401拒绝的请求,并在成功调用/ 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 }}); 

该机制起作用,401失败的请求被第二次触发,问题是他们的“完成”callback不被调用,所以应用程序停滞不前。

您可以使用jQuery.ajaxPrefilter将jqXHR包装到另一个延迟对象中 。

我在jsFiddle上做了一个示例,它显示了它的工作jsFiddle ,并尝试将一些代码用于处理401版本:

 $.ajaxPrefilter(function(opts, originalOpts, jqXHR) { // you could pass this option in on a "retry" so that it doesn't // get all recursive on you. if (opts.refreshRequest) { return; } // our own deferred object to handle done/fail callbacks var dfd = $.Deferred(); // if the request works, return normally jqXHR.done(dfd.resolve); // if the request fails, do something else // yet still resolve jqXHR.fail(function() { var args = Array.prototype.slice.call(arguments); if (jqXHR.status === 401) { $.ajax({ url: '/refresh', refreshRequest: true, error: function() { // session can't be saved alert('Your session has expired. Sorry.'); // reject with the original 401 data dfd.rejectWith(jqXHR, args); }, success: function() { // retry with a copied originalOpts with refreshRequest. var newOpts = $.extend({}, originalOpts, { refreshRequest: true }); // pass this one on to our deferred pass or fail. $.ajax(newOpts).then(dfd.resolve, dfd.reject); } }); } else { dfd.rejectWith(jqXHR, args); } }); // NOW override the jqXHR's promise functions with our deferred return dfd.promise(jqXHR); }); 

这是可行的,因为deferred.promise(object)实际上会覆盖jqXHR上所有的“promise方法”。

注意:任何人发现这一点,如果你附加callbacksuccess:error:在AJAX选项,这段代码将不会按照你的期望。 它假定唯一的callback是使用.fail(callback).done(callback).fail(callback)方法.fail(callback)

正如gnarf的回答指出,成功和错误的callback不会像预期的那样。 如果任何人有兴趣在这里是一个支持successerrorcallback以及承诺风格事件的版本。

 $.ajaxPrefilter(function (options, originalOptions, jqXHR) { // Don't infinitely recurse originalOptions._retry = isNaN(originalOptions._retry) ? Common.auth.maxExpiredAuthorizationRetries : originalOptions._retry - 1; // set up to date authorization header with every request jqXHR.setRequestHeader("Authorization", Common.auth.getAuthorizationHeader()); // save the original error callback for later if (originalOptions.error) originalOptions._error = originalOptions.error; // overwrite *current request* error callback options.error = $.noop(); // setup our own deferred object to also support promises that are only invoked // once all of the retry attempts have been exhausted var dfd = $.Deferred(); jqXHR.done(dfd.resolve); // if the request fails, do something else yet still resolve jqXHR.fail(function () { var args = Array.prototype.slice.call(arguments); if (jqXHR.status === 401 && originalOptions._retry > 0) { // refresh the oauth credentials for the next attempt(s) // (will be stored and returned by Common.auth.getAuthorizationHeader()) Common.auth.handleUnauthorized(); // retry with our modified $.ajax(originalOptions).then(dfd.resolve, dfd.reject); } else { // add our _error callback to our promise object if (originalOptions._error) dfd.fail(originalOptions._error); dfd.rejectWith(jqXHR, args); } }); // NOW override the jqXHR's promise functions with our deferred return dfd.promise(jqXHR); }); 

我为这个用例创build了一个jQuery插件 。 它将gnarf的回答中描述的逻辑封装在一个插件中,另外还允许您指定一个超时,然后再次尝试ajax调用。 例如。

 //this will try the ajax call three times in total //if there is no error, the success callbacks will be fired immediately //if there is an error after three attempts, the error callback will be called $.ajax(options).retry({times:3}).then(function(){ alert("success!"); }); //this has the same sematics as above, except will //wait 3 seconds between attempts $.ajax(options).retry({times:3, timeout:3000}).retry(3).then(function(){ alert("success!"); }); 

这样的事情会为你解决吗? 你只需要返回你自己的延期/承诺,以便原来的不被过早拒绝。

示例/testing用法: http : //jsfiddle.net/4LT2a/3/

 function doSomething() { var dfr = $.Deferred(); (function makeRequest() { $.ajax({ url: "someurl", dataType: "json", success: dfr.resolve, error: function( jqXHR ) { if ( jqXHR.status === 401 ) { return makeRequest( this ); } dfr.rejectWith.apply( this, arguments ); } }); }()); return dfr.promise(); } 

这也是我刚才所面对的一个很大的问题。

我被接受的答案(来自@gnarf)所迷惑,所以我想出了一种让我更容易理解的方法:

  var retryLimit = 3; var tryCount = 0; callAjax(payload); function callAjax(payload) { tryCount++; var newSaveRequest = $.ajax({ url: '/survey/save', type: 'POST', data: payload, headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') }, error: function (xhr, textStatus, errorThrown) { if (textStatus !== 'abort') { console.log('Error on ' + thisAnswerRequestNum, xhr, textStatus, errorThrown); if (tryCount <= retryLimit) { sleep(2000).then(function () { if ($.inArray(thisAnswerRequestNum, abortedRequestIds) === -1) { console.log('Trying again ' + thisAnswerRequestNum); callAjax(payload);//try again } }); return; } return; } } }); newSaveRequest.then(function (data) { var newData = self.getDiffFromObjects(recentSurveyData, data); console.log("Answer was recorded " + thisAnswerRequestNum, newData);//, data, JSON.stringify(data) recentSurveyData = data; }); self.previousQuizAnswerAjax = newSaveRequest; self.previousQuizAnswerIter = thisAnswerRequestNum; } function sleep(milliseconds) { return new Promise((resolve) => setTimeout(resolve, milliseconds)); } 

基本上,我只是将整个Ajax调用callback包装成一个可recursion调用的函数。