传递一个deferred数组给$.when()

下面是一个虚构的例子:http://jsfiddle.net/adamjford/YNGcm/20/

HTML:

<a href="#">Click me!</a>
<div></div>

JavaScript:

function getSomeDeferredStuff() {
var deferreds = [];


var i = 1;
for (i = 1; i <= 10; i++) {
var count = i;


deferreds.push(
$.post('/echo/html/', {
html: "<p>Task #" + count + " complete.",
delay: count
}).success(function(data) {
$("div").append(data);
}));
}


return deferreds;
}


$(function() {
$("a").click(function() {
var deferreds = getSomeDeferredStuff();


$.when(deferreds).done(function() {
$("div").append("<p>All done!</p>");
});
});
});

我希望在所有延迟任务完成后显示“All done!”,但$.when()似乎不知道如何处理一个延迟对象数组。“All done!”首先发生,因为数组不是Deferred对象,因此jQuery继续执行并假定它已经完成。

我知道可以像$.when(deferred1, deferred2, ..., deferredX)一样将对象传递到函数中,但在我试图解决的实际问题中,不知道有多少Deferred对象将在执行中。

130824 次浏览

要将一个值数组传递给任何函数,该函数通常期望它们是单独的参数,请使用Function.prototype.apply,因此在这种情况下,您需要:

$.when.apply($, my_array).then( ___ );

看到http://jsfiddle.net/YNGcm/21/

在ES6中,你可以使用... 传播算子来代替:

$.when(...my_array).then( ___ );

在任何一种情况下,由于不太可能提前知道.then处理程序需要多少正式参数,该处理程序将需要处理arguments数组以检索每个promise的结果。

你可以对数组应用when方法:

var arr = [ /* Deferred objects */ ];


$.when.apply($, arr);

如何使用jQuery延迟数组?< / >

上面的解决方法(谢谢!)并没有正确地解决返回提供给deferred的resolve()方法的对象的问题,因为jQuery使用单独的参数调用done()fail()回调函数,而不是数组。这意味着我们必须使用arguments伪数组来获取由deferred数组返回的所有已解析/拒绝对象,这很难看:

$.when.apply($,deferreds).then(function() {
var objects = arguments; // The array of resolved objects as a pseudo-array
...
};

因为我们传递了一个延迟数组,所以最好返回一个结果数组。返回一个实际数组而不是伪数组也会很好,这样我们就可以使用Array.sort()这样的方法。

下面是一个受when.jswhen.all()方法启发的解决方案,它可以解决这些问题:

// Put somewhere in your scripting environment
if (typeof jQuery.when.all === 'undefined') {
jQuery.when.all = function (deferreds) {
return $.Deferred(function (def) {
$.when.apply(jQuery, deferreds).then(
// the calling function will receive an array of length N, where N is the number of
// deferred objects passed to when.all that succeeded. each element in that array will
// itself be an array of 3 objects, corresponding to the arguments passed to jqXHR.done:
// ( data, textStatus, jqXHR )
function () {
var arrayThis, arrayArguments;


if (Array.isArray(this)) {
arrayThis = this;
arrayArguments = arguments;
}
else {
arrayThis = [this];
arrayArguments = [arguments];
}


def.resolveWith(arrayThis, [Array.prototype.slice.call(arrayArguments)]);
},
// the calling function will receive an array of length N, where N is the number of
// deferred objects passed to when.all that failed. each element in that array will
// itself be an array of 3 objects, corresponding to the arguments passed to jqXHR.fail:
// ( jqXHR, textStatus, errorThrown )
function () {
var arrayThis, arrayArguments;


if (Array.isArray(this)) {
arrayThis = this;
arrayArguments = arguments;
}
else {
arrayThis = [this];
arrayArguments = [arguments];
}


def.rejectWith(arrayThis, [Array.prototype.slice.call(arrayArguments)]);
});
});
}
}

现在你可以简单地传入一个deferred /promises数组,并在回调中得到一个resolved/rejected对象数组,如下所示:

$.when.all(deferreds).then(function(objects) {
console.log("Resolved objects:", objects);
});

如果你正在使用angularJS或Q promise库的一些变体,那么你有一个.all()方法来解决这个确切的问题。

var savePromises = [];
angular.forEach(models, function(model){
savePromises.push(
model.saveToServer()
)
});


$q.all(savePromises).then(
function success(results){...},
function failed(results){...}
);

详见完整API:

https://github.com/kriskowal/q/wiki/API-Reference#promiseall

https://docs.angularjs.org/api/ng/service/$q

作为一种简单的替代方法,它不需要$.when.applyarray,您可以使用以下模式为多个并行promise生成单个promise:

promise = $.when(promise, anotherPromise);

如。

function GetSomeDeferredStuff() {
// Start with an empty resolved promise (or undefined does the same!)
var promise;
var i = 1;
for (i = 1; i <= 5; i++) {
var count = i;


promise = $.when(promise,
$.ajax({
type: "POST",
url: '/echo/html/',
data: {
html: "<p>Task #" + count + " complete.",
delay: count / 2
},
success: function (data) {
$("div").append(data);
}
}));
}
return promise;
}


$(function () {
$("a").click(function () {
var promise = GetSomeDeferredStuff();
promise.then(function () {
$("div").append("<p>All done!</p>");
});
});
});

注:

  • 我在看到有人使用promise = promise.then(newpromise)将承诺按顺序链起来后发现了这一点
  • 缺点是它在幕后创建了额外的承诺对象,并且在最后传递的任何参数都不是很有用(因为它们嵌套在额外的对象中)。对于你想要的东西,它是简短而简单的。
  • 优点是它不需要数组或数组管理。

当调用多个并行AJAX调用时,您有两个选项来处理各自的响应。

  1. 使用同步AJAX调用/一个接一个/不推荐
  2. 使用Promises'数组和接受promises的$.when,当所有的__abc2都成功返回时,它的回调.done将被调用。

例子

.
function ajaxRequest(capitalCity) {
return $.ajax({
url: 'https://restcountries.eu/rest/v1/capital/'+capitalCity,
success: function(response) {
},
error: function(response) {
console.log("Error")
}
});
}
$(function(){
var capitalCities = ['Delhi', 'Beijing', 'Washington', 'Tokyo', 'London'];
$('#capitals').text(capitalCities);


function getCountryCapitals(){ //do multiple parallel ajax requests
var promises = [];
for(var i=0,l=capitalCities.length; i<l; i++){
var promise = ajaxRequest(capitalCities[i]);
promises.push(promise);
}
  

$.when.apply($, promises)
.done(fillCountryCapitals);
}
  

function fillCountryCapitals(){
var countries = [];
var responses = arguments;
for(i in responses){
console.dir(responses[i]);
countries.push(responses[i][0][0].nativeName)
}
$('#countries').text(countries);
}
  

getCountryCapitals()
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<h4>Capital Cities : </h4> <span id="capitals"></span>
<h4>Respective Country's Native Names : </h4> <span id="countries"></span>
</div>

我想建议使用$.each的另一个方法:

  1. 我们可以这样声明ajax函数:

    function ajaxFn(someData) {
    this.someData = someData;
    var that = this;
    return function () {
    var promise = $.Deferred();
    $.ajax({
    method: "POST",
    url: "url",
    data: that.someData,
    success: function(data) {
    promise.resolve(data);
    },
    error: function(data) {
    promise.reject(data);
    }
    })
    return promise;
    }
    }
    
  2. Part of code where we creating array of functions with ajax to send:

    var arrayOfFn = [];
    for (var i = 0; i < someDataArray.length; i++) {
    var ajaxFnForArray = new ajaxFn(someDataArray[i]);
    arrayOfFn.push(ajaxFnForArray);
    }
    
  3. And calling functions with sending ajax:

    $.when(
    $.each(arrayOfFn, function(index, value) {
    value.call()
    })
    ).then(function() {
    alert("Cheer!");
    }
    )
    

如果你正在编译并可以访问ES6,你可以使用扩展语法,它专门将对象的每个可迭代项作为一个离散参数应用,就像$.when()所需要的那样。

$.when(...deferreds).done(() => {
// do stuff
});

MDN Link - Spread Syntax

我有一个非常类似的情况下,我张贴在每个循环,然后设置html标记在一些字段从数字从ajax接收。然后,我需要对这些字段的值(现在更新了)进行求和,并将其放置在total字段中。

因此,问题是,我试图对所有的数字做一个和,但没有数据从异步ajax调用回来。我需要在几个函数中完成这个功能,以便能够重用代码。我的外部函数等待数据,然后再对完全更新的DOM做一些事情。

    // 1st
function Outer() {
var deferreds = GetAllData();


$.when.apply($, deferreds).done(function () {
// now you can do whatever you want with the updated page
});
}


// 2nd
function GetAllData() {
var deferreds = [];
$('.calculatedField').each(function (data) {
deferreds.push(GetIndividualData($(this)));
});
return deferreds;
}


// 3rd
function GetIndividualData(item) {
var def = new $.Deferred();
$.post('@Url.Action("GetData")', function (data) {
item.html(data.valueFromAjax);
def.resolve(data);
});
return def;
}