将Deferred数组传递给$ .when()

时间:2011-04-11 20:39:31

标签: javascript jquery argument-passing jquery-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>");
        });
    });
});

我想要“全部完成!”在所有延迟任务完成后显示,但$.when()似乎不知道如何处理延迟对象数组。 “全部完成!”首先发生的是因为数组不是Deferred对象,所以jQuery继续并且假定它刚刚完成。

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

9 个答案:

答案 0 :(得分:694)

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

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

请参阅http://jsfiddle.net/YNGcm/21/

在ES6中,您可以改为使用... spread operator

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

在任何一种情况下,由于您不太可能事先知道.then处理程序需要多少个正式参数,该处理程序需要处理arguments数组才能检索结果每一个承诺。

答案 1 :(得分:102)

上面的解决方法(谢谢!)没有正确解决返回提供给延迟的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)]);
                });
        });
    }
}

现在你可以简单地传入一个deferreds / promises数组,并在你的回调函数中找回一系列已解析/被拒绝的对象,如下所示:

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

答案 2 :(得分:38)

您可以将when方法应用于数组:

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

$.when.apply($, arr);

How do you work with an array of jQuery Deferreds?

答案 3 :(得分:7)

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

  1. 使用同步AJAX呼叫/一个接一个/不推荐
  2. 使用接受Promises'的{​​{1}}数组和$.when,并且当所有promise成功返回各个响应时,将调用其回调.done
  3. 示例

    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()
    })

答案 4 :(得分:5)

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

promise = $.when(promise, anotherPromise);

e.g。

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)
  • 按顺序查看某个连锁承诺后,我想出了这个
  • 缺点是它会在幕后创建额外的承诺对象,并且最后传递的任何参数都不是很有用(因为它们嵌套在其他对象中)。尽管它很简单,但你想要的是什么。
  • 好处是它不需要阵列或阵列管理。

答案 5 :(得分:4)

我想提出另一个使用$ .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. 我们使用ajax创建函数数组的部分代码:

    var arrayOfFn = [];
    for (var i = 0; i < someDataArray.length; i++) {
        var ajaxFnForArray = new ajaxFn(someDataArray[i]);
        arrayOfFn.push(ajaxFnForArray);
    }
    
  3. 通过发送ajax调用函数:

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

答案 6 :(得分:1)

如果您正在进行转换并且可以访问ES6,则可以使用扩展语法,该语法将对象的每个可迭代项目专门应用为离散参数,就像$.when()需要它一样。

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

MDN Link - Spread Syntax

答案 7 :(得分:0)

如果您正在使用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

答案 8 :(得分:0)

我有一个非常相似的案例,我在每个循环中发布,然后在某些字段中设置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;
    }