当多个Deferred对象传递给jQuery.when时,该方法从一个新的“master”Deferred对象返回Promise,该对象跟踪它已经传递的所有Deferreds的聚合状态。
该方法将
如果解析了主延迟(即所有Deferreds解析),则传递给传递给jQuery.when的所有Deferred的已解析值。例如,当Deferreds是jQuery.ajax()请求时,参数将是请求的jqXHR对象,按照它们在参数列表中给出的顺序:
$.when( $.getJSON('foo'), $.getJSON('bar') ).done(function(foo, bar) {
// foo & bar are jqXHR objects for the requests
});
在其中一个Deferreds被拒绝的多个Deferreds案例中,jQuery.when立即激活其主Deferred的失败回调,即使某些Deferreds在此时仍未解决:
$.when( $.getJSON('foo'), $.getJSON('bar') ).fail(function(req) {
// req is the jqXHR object for one of the failed requests
});
我需要在传递给jQuery.when的所有Deferreds不再“未解决”时触发回调(即所有被解析'或'被拒绝')。我可以使用200 OK代码发送JSON对象(而不是发送带有404 Not Found错误状态代码的JSON)并在done()方法中确定成功/错误,但我更喜欢保持我的API RESTful。我怎么能做到这一点?
答案 0 :(得分:44)
我认为最简单的方法是为每个AJAX请求保留一个辅助Deferred
对象,并确保始终解析 :
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
本身就是一个回调函数,它不需要包含在function() { ... }
块中。
答案 1 :(得分:11)
@Alnitak的回答很聪明,帮助我抹去了我创造的一个黑客,我在某种程度上人为地解决了一个承诺 - 无论其根本结果如何 - 以便我可以使用'when'来批量处理多个请求并使用无论成功/失败,都要“完成”。
我正在“回答”Alnitak的回答,希望能为他的建议提供另一种用途,支持任意数量的潜在承诺。
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,但它应该传达方法。对于一些任意大小的实体集,为每个实体创建一个延迟($ deferred)并将其推送到一个数组($ deferreds),进行异步调用,根据需要添加done / fail但总是包含一个解决这个问题的'always'实体的延期。 NB 'always'接收延迟的解析函数而不是它的调用。
'when'将$ deferreds数组转换为'when'的参数列表,并且由于这组延迟被保证解决(感谢始终),现在可以定义一个'done',它将是无论这些成功/失败是否成功,都会在所有异步调用完成后调用。
答案 2 :(得分:9)
我最近制作了一个可能有帮助的插件。我称之为$.whenAll
。
此扩展程序将所有成功和失败视为进度事件。 在所有承诺完成后,全球承诺得以解决 如果没有错误。否则,全球承诺将被拒绝。
$。whenAll - https://gist.github.com/4341799(tests)
样本用法:
$.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
}
}
)
;
答案 3 :(得分:6)
我的实施:
插件代码:
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]);
});
答案 4 :(得分:3)
这是一个jQuery插件,我通过修改$.when()
的实际核心代码来使用你的语义。由于缺少一个更好的名称,它被称为$.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()
函数将与$.when()
一起使用。除了语义之外,其他所有内容都是100%完全相同。
答案 5 :(得分:0)
Leo Hernandez针对更多一般用例的解决方案的改进,这些用例不仅仅涉及从服务器获取资源,例如可以包括由用户交互或异步jQuery UI调用触发的事件(例如, slideUp()和slideDown())。有关增强的用例,请参阅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()做的事情。此外,我清理了回调函数中的输出,以更好地与原始$ .when()方法的工作方式一致,以防您只想恢复结果而不管状态如何。因此,Leo的解决方案会传递整个延迟对象,然后您需要深入研究以找到所需的信息。
$.whenAll([1, $.Deferred().resolve("Good"), $.Deferred().reject("Bad")])
.done(function (result1, result2, result3) {
// result1 -> 1
// result2 -> "Good"
// result3 -> "Bad"
});
答案 6 :(得分:0)
@Alnitak和@DazWilkin答案很棒!但我个人更喜欢功能风格,所以这里是任意数量的承诺的功能版本:
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
函数代替foreach
。
答案 7 :(得分:0)
我找到了一个解决方案,我在一个时间内有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);
}
);