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

Pass in an array of Deferreds to $.when()

提问人:adamjford 提问时间:4/12/2011 最后编辑:Alnitakadamjford 更新时间:5/5/2021 访问量:132837

问:

下面是一个人为的例子,说明正在发生的事情: 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!”首先发生,因为数组不是 Deferred 对象,因此 jQuery 继续并假设它刚刚完成。$.when()

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

javascript jquery 参数传递 jquery-deferred .when

评论

2赞 Bergi 4/23/2015
related: 等待多个延迟对象完成
0赞 iCollect.it Ltd 5/9/2015
在下面为这个非常古老的问题添加了一个新的、更简单的答案。您无需使用数组或根本不需要获得相同的结果。$.when.apply
0赞 Alnitak 10/12/2015
回滚问题主题,因为它太具体了(这不仅仅是 AJAX 问题)

答:

765赞 Alnitak 4/12/2011 #1

要将值数组传递给通常期望它们为单独参数的任何函数,请使用 ,因此在本例中需要:Function.prototype.apply

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

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

在 ES6 中,您可以改用 spread 运算符...

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

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

评论

4赞 adamjford 4/12/2011
这行得通,太棒了。:)我很惊讶我无法通过谷歌进行如此简单的更改!
9赞 Alnitak 4/12/2011
那是因为它是一个泛型方法,而不是特定于 - 将调用 with 并将参数设置为 的内容。$.whenf.apply(ctx, my_array)fthis == ctxmy_array
4赞 adamjford 4/12/2011
@Alnitak:考虑到我现在已经写 JavaScript 多久了,我有点尴尬,因为我不知道这个方法!
5赞 Alnitak 4/12/2011
FWIW,Eli 对一个 earler 问题的回答中的链接,其中讨论了传递 vs 作为第一个参数,值得一读。不过,在这种特殊情况下,这并不重要。$null
4赞 Tomasz Zieliński 12/18/2012
@Alnitak:是的,但是打字比实现更改时少,而且您是安全的(并不是说在这种情况下可能,但为什么不默认保持不变)。$null$.whenthis
39赞 Eli 4/12/2011 #2

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

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

$.when.apply($, arr);

如何使用 jQuery Deferreds 数组?

评论

0赞 adamjford 4/12/2011
我实际上看到了这个问题,但我想这个问题中的所有额外细节都导致我的问题(就在那里)的答案飞过我的脑海。
1赞 patridge 11/11/2011
@adamjford,如果它让你感觉更好,我发现你的问题更容易理解(首先是我在谷歌上搜索这个确切的问题)。
0赞 adamjford 11/11/2011
@patridge:很高兴听到它帮助了你!
0赞 Garland Pope 8/12/2018
这是一个很好的答案,但我不清楚这如何应用于原始问题中的示例。在咨询了相关的问题后,很明显,“$.when(deferreds).done(function() {”行应该简单地更改为“$.when.apply($,deferreds).done(function() {”。右?
112赞 crispyduck 4/25/2013 #3

上面的解决方法(谢谢!)没有正确解决取回提供给延迟方法的对象的问题,因为 jQuery 使用单个参数而不是数组调用 and 回调。这意味着我们必须使用伪数组来获取延迟数组返回的所有已解析/拒绝的对象,这很丑陋:resolve()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(
            // 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)]);
            });
        });
    }
}

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

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

评论

1赞 Luan Nico 1/14/2016
你的代码只有一个小问题,当数组中只有一个元素时,结果数组只返回该结果,而不是一个具有单个元素的数组(这将破坏需要数组的代码)。要修复它,请使用此函数而不是 .var toArray = function (args) { return deferreds.length > 1 ? $.makeArray(args) : [args]; }Array.prototype.slice.call
-1赞 mastaBlasta 7/22/2014 #4

如果你使用的是 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

评论

5赞 Benjamin Gruenbaum 4/23/2015
这是完全无关紧要的。
0赞 mastaBlasta 4/27/2015
@BenjaminGruenbaum 怎么会这样?所有 javascript promise 库都共享一个类似的 API,展示不同的实现并没有错。我到达此页面是为了寻找 angular 的答案,我怀疑许多其他用户会访问此页面,而不一定处于仅 jquery 环境中。
2赞 Benjamin Gruenbaum 4/27/2015
也就是说,因为 jQuery 的承诺共享这个 API,所以这完全不适合作为 Stack Overflow 上的答案——Angular 也有类似的答案,你可以在那里问。(更不用说,你应该在这里,但哦,好吧)。.map
5赞 iCollect.it Ltd 5/9/2015 #5

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

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赞 Anthony McLin 7/9/2015
如果我错了,请纠正我,但您的方法是有效地嵌套 $.when( $.when( $.when(...) ) ),因此如果有 10 次迭代,您最终会递归嵌套 10 级深度。这似乎不是很并行,因为您必须等待每个级别返回子级的嵌套承诺,然后它才能返回自己的承诺 - 我认为接受的答案中的数组方法要干净得多,因为它使用了内置在 $.when() 方法中的灵活参数行为。
0赞 iCollect.it Ltd 7/9/2015
@AnthonyMcLin:这是为了提供一种更简单的编码替代方案,而不是更好的性能(这在大多数异步编码中可以忽略不计),就像以类似的方式链接调用一样。行为是并行(而不是链接)的行为。请在扔掉有用的替代品之前尝试一下,因为它确实有效:)then()$.when
2赞 iCollect.it Ltd 10/14/2015
@Alnitak:课程的马匹。你当然有权发表意见,但你自己显然没有使用过这个。我个人的观点是基于这种技术的实际应用。它有效并且有用途,所以为什么要从工具箱中扔掉一个基于夸张的工具,例如“大量警告”(一个)和“什么也解决不了”(不是真的 - 它消除了数组处理并简化了不需要返回值的并行承诺的链接,正如你应该知道的那样,无论如何在并行处理情况下很少使用)。反对票应该是“这个答案没有用”:)
1赞 halfer 6/12/2016
嗨,@GoneCoding。请问您不要在回答中添加投票评论吗?这适合评论,但除此之外,噪音会分散对其他良好内容的注意力。谢谢。
1赞 iCollect.it Ltd 6/12/2016
@halfer:我不再发帖了,但我对任何原创作品的无知感到恼火。如今,把所有新想法都留给自己:)
9赞 vinayakj 7/19/2015 #6

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

  1. 使用同步 AJAX 调用/一个接一个/不推荐
  2. 使用数组和 $.when,当所有 s 都成功返回并返回相应的响应时,它会调用 s 和它的回调。Promises'promise.donepromise

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>

评论

2赞 Alnitak 10/12/2015
你的回答超出了范围,你对问题标题的编辑也是如此。OP 已经知道如何进行 AJAX 调用并获取延迟对象的数组。问题的唯一要点是如何将该数组传递给 。$.when
6赞 vinayakj 10/14/2015
我认为用示例详细解释会更好,有可用的选项,为此我认为没有必要投反对票。
2赞 Alnitak 10/14/2015
反对票是1票。甚至建议同步(尽管建议不要同步) 2.示例中的代码质量差(包括在数组上?!for ... in
1赞 vinayakj 10/14/2015
1.同意,应该有 2.不同意 - 没问题,因为数组只包含那些需要的属性(没有额外的属性)。无论如何,thanx(not recommended)for ... in
1赞 Alnitak 10/14/2015
回复:2 - 问题是它可能会被其他人复制,他们无法做出这种保证,或者愚蠢到可以添加 .无论如何,对于非性能关键型代码,最好使用 / 循环,例如 - 工作完成。Array.prototype.mapforpushvar promises = capitalCities.map(ajaxRequest); $.when.apply($, promises).then(fillCountryCapitals)
5赞 Volodymyr Yasinskyi 9/22/2016 #7

我想用$.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赞 relic 10/20/2016 #8

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

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

MDN Link - 传播语法

0赞 Cameron Forward 3/28/2017 #9

我有一个非常相似的情况,我在每个循环中发布,然后从从 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;
    }