如何在自动调用的函数(如.ajax()的beforeSend())中正确使用jQuery Deferred / promise?

我正在努力扩展jQuery的ajaxPrefilter接口,为AJAX调用添加额外的function; 即使用beforeSend()方法中的setRequestHeader()将标题数据添加到XHR请求中。 问题是, ajaxPrefilter和ajax调用本身都可以包含beforeSend选项,并且其中一个或两个都可以包含异步function。

假设我的ajaxPrefilter看起来像这样:

 (function($) { $.ajaxPrefilter(function(options, originalOptions, jqXHR) { var auth = $.cookie("fake_site"), normalizedRequest = $.Deferred(); if (originalOptions.authorizeUser) { options.beforeSend = function() { jqXHR.setRequestHeader("Authorization", "Session " + auth); } // resolving the jqXHR Deferred, statusCode handling, etc. } }); })(jQuery);​ 

现在, 选项originalOptions都可以包含beforeSend()方法,并且我希望options.beforeSend始终先执行。 这似乎是Deferred / promise使用的主要候选者,但是由于beforeSend被自动调用的事实,我不能这样做:

 options.beforeSend = function(jqXHR) { // setting the request header; creating, resolving, and returning the Deferred } $.when(options.beforeSend(jqXHR)).then(function() { originalOptions.beforeSend(); }); 

正如预期的那样,这会导致options.beforeSend两次开火; 由于它的定义语句和$.when调用。

如何使用Deferreds / promises来管理自动调用的方法,例如beforeSend

关于控制beforeSend回调之后发生的事情,只有两个选项:

  • 返回除false之外的任何内容以允许请求继续,或
  • 返回false以取消请求。

您确实可以在回调中发出ajax请求,但是返回的任何内容都会导致立即发出原始(外部)ajax请求,或者取消。 由于回调将完成并无条件地返回,因此不可能通过延迟或其他方式使外部ajax依赖于内部。

对于您想要的效果,您可以在回调中以级联方式发出两个ajax请求(第二个依赖于第一个)并返回false以取消原始请求。

有一天,为什么你可能想采用这种模式可能有一些模糊的原因,但是,如果无条件地丢弃原始请求(通过返回false),它只不过是一种在级联中实现两个ajax请求的复杂方式,而不是涉及beforeSend ,即:

 $.ajax({ //options }).done(function() { $.ajax({ //options }).done(function(){ //do stuff here }); }); 

.pipe()等价物。

总之,似乎不可能使jQuery ajax请求依赖于在其beforeSend回调中编码的另一个异步动作,但是通过不涉及beforeSend标准方法可以实现期望的效果。

以下有点难看,但可以全局处理重新启动原始请求,而不是嵌套每个请求。 例如,如果您需要令牌并且令牌已过期,则此示例依赖于由全局api.getToken()函数返回的承诺。

 $.ajaxSetup({ beforeSend: function(xhr,settings) { var newTokenNeeded = false; var originalSettings=settings; var promise = api.getToken().then(function(data){ if(newTokenNeeded && typeof originalSettings.nested ==='undefined'){ originalSettings.nested=true;//this flag is used to prevent infinite loops $.ajax(originalSettings);//here we re request the original, that we cancelled previously }else{ xhr.setRequestHeader('Authorization', 'Bearer ' +data.t ); } }); if(promise.state()==='pending'){ newTokenNeeded=true; return false; //returning false cancels the original request } } });