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


446

这是正在发生的事情的一个虚构示例: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对象数组。“全做完了!” 首先发生是因为数组不是Deferred对象,所以jQuery继续进行并假定它已经完成。

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



下面为这个非常老的问题添加了一个新的,更简单的答案。您不需要使用数组或根本不需要使用$.when.apply相同的结果。
Gone Coding

回滚了问题的主题,因为它太具体了(这不仅仅是AJAX问题)
Alnitak

Answers:


732

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

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

参见http://jsfiddle.net/YNGcm/21/

在ES6中,可以改用... 传播运算符

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

在这两种情况下,由于您不太可能事先知道.then处理程序需要多少个形式参数,因此该处理程序将需要处理arguments数组以检索每个Promise的结果。


4
这很棒,很棒。:)我很惊讶我无法通过Google疏通这么简单的更改!
adamjford 2011年

9
这是因为它是一个通用的方法,而不是具体到$.when- f.apply(ctx, my_array)将调用fthis == ctx并设置为参数内容my_array
Alnitak

4
@Alnitak:考虑到我现在写JavaScript已有多长时间了,我对这个方法一无所知感到有些尴尬!
adamjford 2011年

5
FWIW,Eli的答案与更早的问题有关的链接,其中以传递$vs null作为第一个参数的讨论值得一读。在这种情况下,这并不重要。
Alnitak

4
@Alnitak:是的,但是$输入类型少于,null并且$.when实现更改时您很安全(不是在这种情况下很可能,但是为什么this默认不更改)。
TomaszZieliński2012年

109

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

$.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(
                function () {
                    def.resolveWith(this, [Array.prototype.slice.call(arguments)]);
                },
                function () {
                    def.rejectWith(this, [Array.prototype.slice.call(arguments)]);
                });
        });
    }
}

现在,您可以简单地传递一组延迟/承诺,并在回调中返回一组已解析/已拒绝的对象,如下所示:

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

6
您可能只想使用resolveWith和rejectWith,以便获得与'this'deferred.resolveWith(this,[Array.prototype.slice.call(arguments)])等相同的原始延期
Jamie Pate 2013年

1
您的代码只有一个小问题,当数组中只有一个元素时,results数组只会返回该结果,而不是只有一个元素的数组(这会破坏期望数组的代码)。要解决此问题,请使用此功能var toArray = function (args) { return deferreds.length > 1 ? $.makeArray(args) : [args]; }代替Array.prototype.slice.call
栾尼科

嗯,这似乎没有发现任何404。
t.mikael.d

找到了原因,.fail应该改为.reject-这样它可以捕获404。
t.mikael.d

38

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

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

$.when.apply($, arr);

您如何使用jQuery Deferreds数组?


我实际上看到了这个问题,但我想该问题中的所有其他细节都导致我的问题的答案(就在那里)正好飞过了我的头。
adamjford 2011年

1
@adamjford,如果让您感觉更好,我发现您的问题更易于使用(首先在我的特定Google搜索中查找此确切问题)。
2011年

@patridge:很高兴听到它对您有帮助!
adamjford

这是一个很好的答案,但我不清楚这如何应用于原始问题中的示例。在查询了链接的问题之后,很明显,应该将行“ $ .when(deferreds).done(function(){”简单地更改为“ $ .when.apply($,deferreds).done(function(){ “。对吗?
Garland Pope

7

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

  1. 使用同步AJAX呼叫/一个接一个/不建议使用
  2. 使用Promises'array and $.when接受promises,并.done在所有promises成功返回各自的响应后调用其回调。

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>


1
您的答案超出范围,因此您对问题标题的编辑也是如此。OP已经知道如何进行AJAX调用并获取一组延迟对象。问题的唯一要点是如何将该数组传递给$.when
Alnitak

5
我认为用示例详细解释会更好,并且有可用的选项。为此,我认为没有必要投票。
vinayakj,2015年

2
下注是针对1.甚至建议同步(尽管建议不要这样做)2.示例中的代码质量较差(包括for ... in在数组上?!)
Alnitak

1
1.同意,应该有 (not recommended)2.不同意- for ... in可以,因为数组仅包含需要的那些属性(没有其他属性)。无论如何
vinayakj 2015年

1
重新:2-问题是它可能会被无法做出保证的其他人复制,或者被愚蠢地添加到Array.prototype。无论如何,对于非性能关键的代码,最好使用.map而不是for/ push循环,例如var promises = capitalCities.map(ajaxRequest); $.when.apply($, promises).then(fillCountryCapitals)-工作完成。
Alnitak

6

作为一个简单的替代方案,不需要$.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)
  • 缺点是它会在后台创建额外的promise对象,并且最后传递的任何参数都不太有用(因为它们嵌套在其他对象中)。对于您想要的,尽管它简短而简单。
  • 好处是不需要阵列或阵列管理。

2
如果我错了,请纠正我,但是您的方法实际上是嵌套$ .when($ .when($ .when(...))),所以如果进行10次迭代,最终将递归嵌套10个深度。这似乎不太并行,因为您必须等待每个级别返回孩子的嵌套承诺,然后才能返回自己的承诺-我认为已接受答案中的数组方法使用了内置的灵活参数行为,因此更加简洁$ .when()方法。
安东尼·麦克林

@AnthonyMcLin:这旨在为编码提供一种更简单的替代方法,而不是提供更好的性能(在大多数异步编码中可以忽略),就像then()以类似方式对链接调用进行的操作一样。with的行为$.when是按其平行(未链接)的方式进行操作。请先尝试一下,然后再放弃有用的替代方法,因为它确实起作用:)
Gone Coding

2
@Alnitak:赛马。您当然有权征求意见,但是您显然自己并没有使用过。我个人的观点是基于该技术的实际使用。它可以工作并且有用途,所以为什么要根据诸如“警告”的负载(一个)和“什么都不解决”之类的夸张从工具箱中抛出一个工具(不是真的-它消除了数组处理并简化了在返回时返回的并行promise的链接)不需要值,您应该知道,无论如何在并行处理案例中很少使用这些值)。投票应该被认为是“这个答案没有用的” :)
Gone Coding

1
嗨@GoneCoding。我是否可以要求您不要在评论中添加投票评论?这适合发表评论,但否则,杂乱的声音会干扰原本不错的内容。谢谢。
Halfer

1
@halfer:我不再发布任何内容,但是我对任何原始内容所显示的无知感到恼火。如今将所有新想法保留给自己:)
Gone Coding

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

1

如果您正在转译并可以访问ES6,则可以使用传播语法,该语法专门将对象的每个可迭代项作为离散参数应用,恰恰是$.when()需要它的方式。

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

MDN链接-传播语法


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


4
这是完全不相关的。
本杰明·格伦鲍姆

@BenjaminGruenbaum怎么样?所有的JavaScript Promise库都共享相似的API,并且显示不同的实现也没有错。我到达此页面寻找角度的答案,我怀疑许多其他用户将到达此页面,并且不一定处于纯jquery环境中。
mastaBlasta 2015年

2
也就是说,由于jQuery的承诺共享此API,因此这完全不适合作为Stack Overflow的答案-Angular也有类似的答案,您可以在那询问。(更不用说,您应该.map在这里,但哦)。
本杰明·格伦鲍姆

0

我有一个非常相似的案例,我在每个循环中发布内容,然后在从ajax接收到的数字中的某些字段中设置html标记。然后,我需要对这些字段的值(现在更新)进行求和,然后将其放置在总计字段中。

因此,问题在于我试图对所有数字求和,但尚未从异步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;
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.