【问题标题】:Pass in an array of Deferreds to $.when()将延迟数组传递给 $.when()
【发布时间】:2011-08-03 09:25:56
【问题描述】:

这是一个人为的例子: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) 之类的函数,但不知道在我要解决的实际问题中执行时会有多少延迟对象。

【问题讨论】:

  • 为下面这个非常古老的问题添加了一个新的、更简单的答案。您确实不需要需要使用数组或$.when.apply 来获得相同的结果。
  • 回滚问题主题,因为它太具体了(这不仅仅是一个 AJAX 问题)

标签: javascript jquery argument-passing jquery-deferred .when


【解决方案1】:

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

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

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

在 ES6 中,您可以使用 ... spread operator 代替:

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

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

【讨论】:

  • 这行得通,太棒了。 :) 我很惊讶我无法通过 Google 挖掘出如此简单的更改!
  • 那是因为它是一个通用方法,而不是特定于 $.when - f.apply(ctx, my_array) 将使用 this == ctx 调用 f 并将参数设置为 @ 的 contents 987654333@.
  • @Alnitak:考虑到我现在写 JavaScript 有多久了,我不知道那个方法有点尴尬!
  • FWIW,Eli 对早期问题的回答中的链接,其中讨论了将 $null 作为第一个参数传递的讨论,值得一读。不过,在这种特殊情况下,这并不重要。
  • @Alnitak: 是的,但是$ 的输入比null 少,当$.when 实现发生变化时你是安全的(不是说在这种情况下很可能,但为什么不保留this默认不变)。
【解决方案2】:

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

【讨论】:

  • 您的代码有一个小问题,当数组中只有一个元素时,结果数组只返回该结果,而不是具有单个元素的数组(这会破坏需要一个数组)。要修复它,请使用此函数 var toArray = function (args) { return deferreds.length &gt; 1 ? $.makeArray(args) : [args]; } 而不是 Array.prototype.slice.call
【解决方案3】:

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

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

$.when.apply($, arr);

How do you work with an array of jQuery Deferreds?

【讨论】:

  • 我确实看到了这个问题,但我猜那个问题中的所有额外细节导致我的问题的答案(就在那里)飞到我的头上。
  • @adamjford,如果它让你感觉好些,我发现你的问题更容易理解(首先在我的特定谷歌搜索中搜索这个确切的问题)。
  • @patridge:很高兴听到它帮助了你!
  • 这是一个很好的答案,但我不清楚这如何应用于原始问题中的示例。在咨询了链接的问题后,很明显“$.when(deferreds).done(function() {” 应该简单地更改为“$.when.apply($,deferreds).done(function() { “。对吧?
【解决方案4】:

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

  1. 使用同步 AJAX 调用/一个接一个/不推荐
  2. 使用Promises' 数组和$.when,它接受promises,当所有promises 都成功返回并带有各自的响应时,它的回调.done 被调用。

示例

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>

【讨论】:

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

作为一个简单的替代方案,不需要$.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) 进行承诺后,我发现了这一点
  • 缺点是它在幕后创建了额外的承诺对象,并且最后传递的任何参数都不是很有用(因为它们嵌套在其他对象中)。尽管它简短而简单,但满足您的需求。
  • 好处是它不需要阵列或阵列管理。

【讨论】:

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

我想用 $.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!");
        }
    )
    

【讨论】:

    【解决方案7】:

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

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

    MDN Link - Spread Syntax

    【讨论】:

      【解决方案8】:

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

      【讨论】:

        【解决方案9】:

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

        【讨论】:

        • 这完全无关。
        • @BenjaminGruenbaum 怎么样?所有 javascript Promise 库共享一个相似的 API,显示不同的实现并没有错。我到达这个页面寻找角度的答案,我怀疑许多其他用户会到达这个页面,不一定是在一个只有 jquery 的环境中。
        • 即,因为 jQuery 的承诺 共享此 API,这完全不适合作为 Stack Overflow 上的答案 - Angular 有类似的答案,你可以在那里提问。 (更不用说,你应该在这里.map 但是哦)。
        猜你喜欢
        • 1970-01-01
        • 2012-08-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-10-20
        • 1970-01-01
        相关资源
        最近更新 更多