Передайте массив Deferreds в $.когда()
вот надуманный пример того, что происходит: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>");
});
});
});
Я хочу "все сделано!"появиться после того, как все отложенные задачи будут выполнены, но $.when()
похоже, не знает, как обрабатывать массив отложенных объектов. - Все сделано!"происходит сначала, потому что массив не является отложенным объектом, поэтому jQuery идет вперед и предполагает, что это просто сделанный.
Я знаю, что можно было бы передать объекты в функцию, как $.when(deferred1, deferred2, ..., deferredX)
но неизвестно, сколько отложенных объектов будет при выполнении в реальной проблеме, которую я пытаюсь решить.
9 ответов:
чтобы передать массив значений в любой функция, которая обычно ожидает, что они будут отдельными параметрами, используйте
Function.prototype.apply
, Так что в этом случае вам нужно:$.when.apply($, my_array).then( ___ );
см.http://jsfiddle.net/YNGcm/21/
в ES6, вы можете использовать
...
распространение оператор вместо:$.when(...my_array).then( ___ );
в любом случае, так как маловероятно, что вы будете знать заранее, сколько формальных параметров
.then
проводник потребует, что обработчик должен будет обработатьarguments
массив для того, чтобы получить результат в каждом обещание.
обходные пути выше (спасибо!) не правильно решать проблему возврата объектов, предоставленных отложенным
resolve()
метод, потому что jQuery называетdone()
иfail()
обратные вызовы с индивидуальными параметрами, а не массив. Это означает, что мы должны использоватьarguments
псевдо-массив, чтобы получить все разрешенные / отклоненные объекты, возвращаемые массивом deferreds, что некрасиво:$.when.apply($,deferreds).then(function() { var objects=arguments; // The array of resolved objects as a pseudo-array ... };
так как мы передали в массив deferreds, было бы неплохо получить обратно массив о результатах. Также было бы неплохо вернуть фактический массив вместо псевдо-массива, чтобы мы могли использовать такие методы, как
Array.sort()
.вот решение, вдохновленное когда.js ' s
when.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( function () { def.resolveWith(this, [Array.prototype.slice.call(arguments)]); }, function () { def.rejectWith(this, [Array.prototype.slice.call(arguments)]); }); }); } }
теперь вы можете просто передать массив deferreds / promises и получить обратно массив разрешенных / отклоненных объектов в вашем обратном вызове, например:
$.when.all(deferreds).then(function(objects) { console.log("Resolved objects:", objects); });
Вы можете применить
when
метод для вашего выбора:var arr = [ /* Deferred objects */ ]; $.when.apply($, arr);
при вызове нескольких параллельных вызовов AJAX у вас есть два варианта обработки соответствующих ответов.
- использовать синхронный вызов AJAX/ один за другим/ не рекомендуется
- использовать
Promises'
массив$.when
принимаетpromise
s и его обратный вызов.done
вызывается, когда всеpromise
s возвращаются успешно с соответствующим ответы.пример
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>
как простая альтернатива, которая не требует
$.when.apply
илиarray
, вы можете использовать следующий шаблон для создания одного обещания для нескольких параллельных обещаний: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, как:
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; } }
часть кода, где мы создаем массив функций с ajax для отправки:
var arrayOfFn = []; for (var i = 0; i < someDataArray.length; i++) { var ajaxFnForArray = new ajaxFn(someDataArray[i]); arrayOfFn.push(ajaxFnForArray); }
и вызов функций с отправкой ajax:
$.when( $.each(arrayOfFn, function(index, value) { value.call() }) ).then(function() { alert("Cheer!"); } )
Если вы транспилируете и имеете доступ к ES6, вы можете использовать синтаксис spread, который специально применяет каждый итерационный элемент объекта в качестве дискретного аргумента, точно так же, как
$.when()
в ней нуждается.$.when(...deferreds).done(() => { // do stuff });
Если вы используете 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
У меня был случай, очень похожий, когда я размещал в каждом цикле, а затем устанавливал разметку html в некоторых полях из чисел, полученных из ajax. Затем мне нужно было сделать сумму (теперь обновленных) значений этих полей и поместить в общее поле.
таким образом, проблема заключалась в том, что я пытался сделать сумму по всем номерам, но данные еще не вернулись из асинхронных вызовов 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; }