cip*_*pak 37 ajax jquery jquery-deferred
我正在尝试实现一个重试ajax请求的系统,这些请求因临时原因而失败.在我的例子中,它是关于在调用刷新web服务以恢复会话之后重试因401会议状态代码而失败的请求,因为会话已过期.
问题是成功重试时不会调用"done"回调,这与调用的"success"ajax选项回调不同.我在下面编写了一个简单的例子:
$.ajaxSetup({statusCode: {
404: function() {
this.url = '/existent_url';
$.ajax(this);
}
}});
$.ajax({
url: '/inexistent_url',
success: function() { alert('success'); }
})
.done(function() {
alert('done');
});
Run Code Online (Sandbox Code Playgroud)
是否有办法在成功重试时调用完成式回调?我知道延期不能被"拒绝"后"解决",是否有可能阻止拒绝?或者可能将原始延迟的doneList复制到新的延迟?我没有想法:)
下面是一个更现实的例子,我试图排队所有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
}});
Run Code Online (Sandbox Code Playgroud)
该机制工作,401失败的请求第二次被触发,问题是他们的'完成'回调没有被调用,所以应用程序停止.
gna*_*arf 50
您可以使用jQuery.ajaxPrefilter
将jqXHR包装在另一个延迟对象中.
我做了一个示例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);
});
Run Code Online (Sandbox Code Playgroud)
这是有效的,因为它deferred.promise(object)
实际上会覆盖jqXHR上的所有"promise方法".
注意:对于发现此问题的任何其他人,如果您使用success:
和error:
在ajax选项中附加回调,则此代码段将无法按预期方式工作.它假设唯一的回调是使用jqXHR 的.done(callback)
和.fail(callback)
方法附加的回调.
rya*_*yan 15
正如gnarf的答案所指出的那样,成功和错误回调将不会像预期的那样表现.如果有人有兴趣在这里是既支持版本success
和error
回调以及承诺的风格事件.
$.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);
});
Run Code Online (Sandbox Code Playgroud)
我为这个用例创建了一个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!");
});
Run Code Online (Sandbox Code Playgroud)
这样的事能为你效劳吗?您只需要返回自己的延期/承诺,以便原来的不会过早拒绝.
示例/测试用法: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();
}
Run Code Online (Sandbox Code Playgroud)