Передайте массив отложенных в $ .when ()

403

Здесь надуманный пример того, что происходит: 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), но неизвестно, сколько отложенных объектов там будет выполняться в реальной проблеме, которую я пытаюсь решить.

  • 0
    Ниже добавлен новый, более простой ответ на этот очень старый вопрос. Вам не нужно вообще использовать массив или $.when.apply чтобы получить тот же результат.
Показать ещё 1 комментарий
Теги:
jquery-deferred
.when
argument-passing

9 ответов

703
Лучший ответ

Чтобы передать массив значений любой функции, которая обычно ожидает их отдельных параметров, используйте Function.prototype.apply, поэтому в этом случае вам нужно:

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

См. http://jsfiddle.net/YNGcm/21/

В ES6 вы можете вместо этого использовать оператор ... :

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

В любом случае, поскольку маловероятно, что вы заранее узнаете, сколько формальных параметров потребует обработчик .then, этот обработчик должен обработать массив arguments, чтобы получить результат каждого обещания.

  • 4
    Это работает, круто. :) Я поражен, что не смог найти такую простую перемену через Google!
  • 8
    это потому, что это универсальный метод, не специфичный для $.when f.apply(ctx, my_array) - f.apply(ctx, my_array) будет вызывать f с this == ctx и аргументами, установленными на содержимое my_array .
Показать ещё 13 комментариев
98

Обходные пути выше (спасибо!) неправильно решают проблему возврата объектов, предоставленных отложенному методу resolve(), потому что jQuery вызывает обратные вызовы done() и fail() с отдельными параметрами, а не с массивом. Это означает, что мы должны использовать псевдо-массив arguments, чтобы получить все разрешенные/отклоненные объекты, возвращенные массивом отложенных слов, что является уродливым:

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

Поскольку мы прошли в массиве отложенных периодов, было бы неплохо вернуть массив результатов. Также было бы неплохо вернуть реальный массив вместо псевдо-массива, поэтому мы можем использовать такие методы, как Array.sort().

Вот решение, основанное на методе when.js 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)]);
                });
        });
    }
}

Теперь вы можете просто передать массив отложенных / promises и вернуть массив разрешенных/отклоненных объектов в ваш обратный вызов, например:

$.when.all(deferreds).then(function(objects) {
    console.log("Resolved objects:", objects);
});
  • 6
    Возможно, вы захотите использовать resolWith и rejectWith только для того, чтобы получить те же исходные отложенные значения, что и для «this» deferred.resolveWith (this, [Array.prototype.slice.call (arguments)]) и т. Д.
  • 1
    Есть небольшая проблема с вашим кодом, когда в массиве есть только один элемент, массив результатов возвращает только этот результат, а не массив с одним элементом (который нарушит код, ожидающий массив). Чтобы исправить это, используйте эту функцию var toArray = function (args) { return deferreds.length > 1 ? $.makeArray(args) : [args]; } вместо Array.prototype.slice.call .
Показать ещё 3 комментария
38

Вы можете применить метод when к вашему массиву:

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

$.when.apply($, arr);

Как вы работаете с массивом отложенных jQuery?

  • 1
    Вау, я медленный сегодня ...
  • 0
    Я действительно видел этот вопрос, но я предполагаю, что все дополнительные детали в этом вопросе заставили ответ на мою проблему (которая была прямо там) пролететь прямо над моей головой.
Показать ещё 3 комментария
5

При вызове нескольких параллельных вызовов AJAX у вас есть два варианта обработки соответствующих ответов.

  1. Использовать синхронный вызов AJAX/один за другим/не рекомендуется
  2. Используйте массив Promises' и $.when который принимает promise и его обратный вызов .done когда все promise успешно возвращаются с соответствующими ответами.

пример

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 Native Names : </h4> <span id="countries"></span>
</div>
  • 0
    ваш ответ перегружен, как и ваше изменение к названию вопроса. ОП уже знал, как делать вызовы AJAX и получать массив отложенных объектов. Единственный вопрос заключается в том, как передать этот массив в $.when .
  • 4
    Я думал, что объяснение подробно с примером было бы лучше, с доступными вариантами. И для этого я не думаю, что понизить голосование было необходимо.
Показать ещё 6 комментариев
5

В качестве простой альтернативы, для которой не требуется $.when.apply или array, вы можете использовать следующий шаблон для создания единого обещания для нескольких параллельных promises:

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>");
        });
    });
});

Примечания:

  • Я понял это, увидев последовательную цепочку promises, используя promise = promise.then(newpromise)
  • Недостатком является то, что он создает дополнительные объекты обещания за кулисами, и любые параметры, переданные в конце, не очень полезны (поскольку они вложены внутри дополнительных объектов). Для чего вы хотите, хотя это коротко и просто.
  • Поверхность - это не требует управления массивами или массивами.
  • 2
    Поправьте меня, если я ошибаюсь, но ваш подход эффективно вкладывает $ .when ($ .when ($ .when (...))), так что вы получите рекурсивно вложенные 10 уровней глубиной, если есть 10 итераций. Это не кажется очень параллельным, так как вам нужно подождать, пока каждый уровень вернет вложенное обещание ребенка, прежде чем он сможет вернуть свое собственное обещание - я думаю, что подход массива в принятом ответе намного чище, поскольку он использует гибкое поведение параметров, встроенное в метод $ .when ().
  • 0
    @AnthonyMcLin: это предназначено для того, чтобы предоставить более простую альтернативу кодированию, а не лучшую производительность (что пренебрежимо мало для большинства асинхронных кодировок), как это делается аналогичным образом с вызовами chaining then() . Поведение с $.when должно действовать как параллельное (не цепное). Пожалуйста, попробуйте, прежде чем выбросить полезную альтернативу, как это работает :)
Показать ещё 8 комментариев
3

Я хочу предложить другое с помощью $.each:

  • Мы можем объявить функцию 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!");
        }
    )
    
1

Если вы транслируете и имеете доступ к ES6, вы можете использовать синтаксис распространения, который специфически применяет каждый повторяемый элемент объекта как дискретный аргумент, просто как ему нужно $.when().

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

MDN Link - Синтаксис распространения

0

У меня был случай, очень похожий, когда я отправлял в каждом цикле, а затем устанавливал разметку html в некоторых полях из чисел, полученных из ajax. Затем мне нужно было сделать сумму (обновленных) значений этих полей и поместить в полное поле.

Таким образом, проблема заключалась в том, что я пытался делать сумму на всех числах, но никаких данных еще не было получено из асинхронных вызовов async. Мне нужно было выполнить эту функциональность в нескольких функциях, чтобы повторно использовать код. Моя внешняя функция ожидает данных до того, как я перейду и сделаю некоторые вещи с полностью обновленной версией 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;
    }
0

Если вы используете angularJS или какой-то вариант библиотеки обещаний Q, у вас есть метод .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

  • 4
    Это совершенно не имеет значения.
  • 0
    @BenjaminGruenbaum Как так? Все библиотеки обещаний JavaScript имеют одинаковый API, и нет ничего плохого в том, чтобы показать разные реализации. Я зашел на эту страницу в поисках ответа для angular, и я подозреваю, что многие другие пользователи дойдут до этой страницы и не обязательно будут в среде только jquery.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню