jQuery.when – 当所有的延期不再是“未解决”(解决或拒绝)的callback?
当多个Deferred对象传递给jQuery.when时 ,该方法从一个新的“主”Deferred对象返回Promise,追踪所有Deferred对象的聚合状态。
该方法将要么
- 解决它的主人所有的延期解决,或延期
- 拒绝其主人推迟一个延期被拒绝。
如果主Deferred被parsing(即,所有的Deferredsparsing),则传递所有传递给jQuery的Deferreds的parsing值。 例如,当Deferreds是jQuery.ajax()请求时,参数将是请求的jqXHR对象,按参数列表中的顺序排列:
$.when( $.getJSON('foo'), $.getJSON('bar') ).done(function(foo, bar) { // foo & bar are jqXHR objects for the requests });
在多个Deferreds情况下,其中一个Deferreds被拒绝,jQuery.如果立即为其主Deferred处理失败callback,即使某些Deferreds在该点仍然可能未parsing:
$.when( $.getJSON('foo'), $.getJSON('bar') ).fail(function(req) { // req is the jqXHR object for one of the failed requests });
当所有的Deferreds传递给jQuery.when不再是'未解决'(即所有的“解决”或“被拒绝”)时,我需要发起一个callback。 我可以用200 OK代码发送JSON对象(而不是发送带有404 Not Found错误状态代码的JSON),并确定done()方法中的成功/错误,但我更愿意保持我的API RESTful。 我怎样才能做到这一点?
我认为最简单的方法是为每个AJAX请求保留一个辅助Deferred
对象,并确保始终parsing该对象:
var d1 = $.Deferred(); var d2 = $.Deferred(); var j1 = $.getJSON(...).complete(d1.resolve); var j2 = $.getJSON(...).complete(d2.resolve); $.when(j1, j2).done( only fires if j1 AND j2 are resolved ); $.when(d1, d2).done(function() { // will fire when j1 AND j2 are both resolved OR rejected // check j1.isResolved() and j2.isResolved() to find which failed });
这是利用额外的AJAX .complete()
方法,jQuery增加了对AJAX方法的承诺,这个方法被称为已解决和被拒绝的承诺。
注意: d1.resolve
就是一个callbackfunction() { ... }
,它不需要封装在function() { ... }
。
@Annitak的答案很聪明,帮助我抹去了我创造的一种破解,其中我有些人为地解决了一个承诺 – 无论潜在的结果 – 为了我可以使用'when'来批量处理多个请求并使用'done'无论成败如何都要继续。
我正在“回答”阿尔尼塔克的答案,希望为他的build议提供另一种用途,以支持任意数量的基本承诺。
var asyncFunc, entity, entities, $deferred, $deferreds; // ... foreach (entity in entities) { $deferred = $.Deferred(); $deferreds.push($deferred); asyncFunc(entity).done(...).fail(...).always($deferred.resolve); } // ... $.when.apply($, $deferreds).done(...)
这是伪JavaScript,但它应该传达的方法。 对于一些任意大小的实体集,为每个实体创build一个deferred($ deferred)并将其推送到一个数组($ deferreds),进行asynchronous调用,根据需要添加done / fail,但始终包含一个“always”来解决这个问题实体的$延期。 注意 “总是”收到延期的parsing函数而不是它的调用。
'when'将$ deferreds数组转换为'when'的参数列表,并且,由于这组延迟被保证parsing(归功于always),现在可以定义一个'done',它将被调用一次asynchronous调用完成,不pipe这些成功/失败。
我最近做了一个可以帮助的插件。 我叫它$.whenAll
。
这个扩展把所有的成功和失败都视为进步事件。 所有的承诺完成后,如果没有错误,全球的承诺就会得到解决。 否则,全球承诺被拒绝。
$ .whenAll – https://gist.github.com/4341799 ( testing )
示例用法:
$.whenAll($.getJSON('foo'), $.getJSON('bar')) .then( doneCallback ,failcallback // progress callback // the only problem is $.ajax.done/fail states call their callbacks // with params in different locations (except for state) ,function(data, state, jqXhr) { if (state == 'success') { // do happy stuff } else { // error (fail) // `data` is actually the jqXhr object for failed requests // `jqXhr` is the text of the error "Not Found" in this example } } ) ;
我的实现:
插件代码:
jQuery.whenAll = function (deferreds) { var lastResolved = 0; var wrappedDeferreds = []; for (var i = 0; i < deferreds.length; i++) { wrappedDeferreds.push(jQuery.Deferred()); deferreds[i].always(function() { wrappedDeferreds[lastResolved++].resolve(arguments); }); } return jQuery.when.apply(jQuery, wrappedDeferreds).promise(); };
要使用它:
jQuery.whenAll([jQuery.get('/your-resource'), jQuery.get('/your-resource')]) .done( function(result1, result2) { console.log(result1[1]); console.log(result2[1]); });
看看小提琴: http : //jsfiddle.net/LeoJH/VMQ3F/
下面是我通过修改$.when()
的实际核心代码来使用你的语义的一个jQuery插件。 对于一个更好的名字叫做$.myWhen()
:
(function($) { $.myWhen = function( subordinate /* , ..., subordinateN */ ) { var i = 0, responseValues = Array.prototype.slice.call( arguments ), length = responseValues.length, // the count of uncompleted subordinates remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, // the master Deferred. If responseValues consist of only a single Deferred, just use that. deferred = remaining === 1 ? subordinate : jQuery.Deferred(), // Update function for all resolve, reject and progress values updateFunc = function( i, contexts, values ) { return function( value ) { contexts[ i ] = this; values[ i ] = arguments.length > 1 ? Array.prototype.slice.call( arguments ) : value; if( values === progressValues ) { deferred.notifyWith( contexts, values ); } else if ( !( --remaining ) ) { deferred.resolveWith( contexts, values ); } }; }, progressValues, progressContexts, responseContexts; // add listeners to Deferred subordinates; treat others as resolved if ( length > 1 ) { progressValues = new Array( length ); progressContexts = new Array( length ); responseContexts = new Array( length ); for ( ; i < length; i++ ) { if ( responseValues[ i ] && jQuery.isFunction( responseValues[ i ].promise ) ) { responseValues[ i ].promise() .always( updateFunc( i, responseContexts, responseValues ) ) .progress( updateFunc( i, progressContexts, progressValues ) ); } else { --remaining; } } } // if we're not waiting on anything, resolve the master if ( !remaining ) { deferred.resolveWith( responseContexts, responseValues ); } return deferred.promise(); }; })(jQuery);
只要将这段代码放在jQuery加载的地方, $.myWhen()
函数将在$.myWhen()
旁边。 除了语义之外,其他一切都完全一样。
Leo Hernandez的解决scheme是针对不仅仅涉及从服务器获取资源(例如可以包括由用户交互触发的事件)或asynchronousjQuery UI调用(例如,slideUp()和slideDown())的更一般用例的解决scheme。 有关增强的用例,请参阅https://jsfiddle.net/1trucdn3/ 。
$.whenAll = function (deferreds) { var lastResolved = 0; var wrappedDeferreds = []; for (var i = 0; i < deferreds.length; i++) { wrappedDeferreds.push($.Deferred()); if (deferreds[i] && deferreds[i].always) { deferreds[i].always(wrappedDeferreds[lastResolved++].resolve); } else { wrappedDeferreds[lastResolved++].resolve(deferreds[i]); } } return $.when.apply($, wrappedDeferreds).promise(); };
改进使我们能够将非延迟值传递给数组参数。 这是你可以用$ .when()做的事情。 另外,我清理了在callback函数中获得的输出,以便与原始的$ .when()方法更直接地联系起来,以防止只是想返回结果而不考虑状态。 因此,Leo的解决scheme会传递整个延迟对象,然后您需要深入查找所需的信息。
$.whenAll([1, $.Deferred().resolve("Good"), $.Deferred().reject("Bad")]) .done(function (result1, result2, result3) { // result1 -> 1 // result2 -> "Good" // result3 -> "Bad" });
@Alnitak和@DazWilkin的答案是伟大的! 但我个人更喜欢function风格,所以这里是任意数量的承诺的function版本:
var entities; // ... var deferreds = entities.map(function() { var deferred = $.Deferred(); asyncFunc(this).done(...).fail(...).always(deferred.resolve); return deferred; } // ... $.when.apply($, deferreds).done(...)
相比@DazWilkin答案,我使用map
function,而不是foreach
。
我发现了一个解决scheme,当我有2个请求时,即使其中一个请求失败,也可以访问个人的成功:
$.when ( $.getJSON(...).then(function (results) { console.log('SUCCESS REQUEST 1 BY ITSELF', results); }), $.getJSON(...).then(function (results) { console.log('SUCCESS REQUEST 2 BY ITSELF', results); }) ).then ( function (results1, results2) { console.log('BOTH REQUESTS SUCCESSFUL...'); console.log('results1', results1); console.log('results2', results2); }, function (error1, error2) { console.log('AT LEAST 1 REQUEST FAILED...'); console.log('error1', error1); console.log('error2', error2); } );