【问题标题】:jQuery.when - Callback for when ALL Deferreds are no longer 'unresolved' (either resolved or rejected)?jQuery.when - 当所有延迟不再“未解决”(已解决或被拒绝)时回调?
【发布时间】:2011-08-15 00:59:04
【问题描述】:

当将多个 Deferred 对象传递给 jQuery.when 时,该方法从一个新的“主” Deferred 对象返回 Promise,该对象跟踪它已传递的所有 Deferred 的聚合状态。

该方法将要么

  1. 在所有 Deferred 解决后立即解决其 master Deferred,或者
  2. 只要其中一个 Deferred 被拒绝,就拒绝其 master Deferred。

如果主 Deferred 已解析(即所有 Deferred 解析),则将传递给 jQuery.when 的所有 Deferred 的解析值传递。例如,当 Deferred 是 jQuery.ajax() 请求时,参数将是请求的 jqXHR 对象,按照它们在参数列表中给出的顺序:

$.when( $.getJSON('foo'), $.getJSON('bar') ).done(function(foo, bar) {

    // foo & bar are jqXHR objects for the requests

});

在其中一个 Deferred 被拒绝的多个 Deferred 情况下,jQuery.when IMMEDIATELY FIRES 为其主 Deferred 的失败回调,即使此时某些 Deferred 可能仍未解决:

$.when( $.getJSON('foo'), $.getJSON('bar') ).fail(function(req) {

    // req is the jqXHR object for one of the failed requests

});

当所有传递给 jQuery.when 的 Deferred 不再“未解决”(即,所有都被“解决”或“拒绝”)时,我需要触发回调。我可以发送带有 200 个 OK 代码的 JSON 对象(而不是发送带有 404 Not Found 错误状态代码的 JSON)并在 done() 方法中确定成功/错误,但我更愿意让我的 API 保持 RESTful。我怎样才能做到这一点?

【问题讨论】:

  • “我需要触发回调”——回调是什么,是失败还是成功?也许您可以提供一个响应流的示例,以及它们何时应该/不应该解决或失败,即page1 --> 200 got page, page2 --> 400 invalid page, page3 --> 200 got page - 例如,在这种情况下应该调用 .fail 似乎很明显。
  • @Gary Green - 我正在对两个 JSON 资源进行并行请求;如果两者都成功(返回 200 OK),我正在处理两者。如果一个失败(返回 404 Not Found)并且一个成功,我将显示一种错误消息。如果两者都失败,我将显示另一种错误消息。
  • 对于任何感兴趣的人,Alan 在 jQuery 的 bug 跟踪器上提交了一个(名称不佳的)功能请求:Ticket #9386 "DEFERREDS FEATURE REQUEST"。它以“wontfix”的形式很快就关闭了,其中最重要的评论来自 jaubourg:-1, Start with a plugin, I'll gladly incorporate this in ajaxHooks (which I'd like to have some deferred helpers too)——很高兴在这里看到这样的插件作为答案!
  • 我为此向 jQuery 提出了另一个功能请求以及对$.when() 的相关增强:OPTIONS PARAMETER FOR $.WHEN() TO PROVIDE ALTERNATIVE SEMANTICS。它也已关闭,但 jaubourg 添加了很好的分析和建议,这将有助于任何对此问题感兴趣的人。

标签: javascript jquery rest jquery-deferred


【解决方案1】:

我认为最简单的方法是为每个 AJAX 请求保留一个辅助 Deferred 对象,并确保那个始终得到解决:

var d1 = $.Deferred();
var d2 = $.Deferred();

var j1 = $.getJSON(...).complete(d1.resolve);
var j2 = $.getJSON(...).complete(d2.resolve);

$.when(j1, j2).done(function() {
     // only fires if j1 AND j2 are resolved
});

$.when(d1, d2).done(function() {
     // will fire when j1 AND j2 are both resolved OR rejected
     // check j1.isResolved() and j2.isResolved() to find which failed
});

这是利用额外的 AJAX .complete() 方法,该方法是 jQuery 添加到其 AJAX 方法的承诺中的,对于已解决和拒绝的承诺都会调用该方法。

注意:d1.resolve 本身就是一个回调函数,它不需要包装在 function() { ... } 块中。

【讨论】:

  • 漂亮,我几乎没有注意到 when() 函数内部变量的差异:)
  • ...如果您正在使用延迟对象(与返回 jqXHR 对象的 $.getJSON() 不同),则使用 .always() 而不是 .complete() :var p3 = $.post(...).always(d3.resolve);
  • 为什么它不适用于 $.ajax(..) 调用?我打开了一个新问题,但巨魔已将其标记为重复。我正在使用参数函数进行 ajax 调用,而不是直接在 when() 中调用 $.ajax(): function loadCommodities(adm0code) {$.ajax({url: "api" + adm0code, success: function (data) {set一个变量}});}
  • 我得到 $.ajax(...).complete 不是一个函数
  • @JoãoPimentelFerreira 您的编辑不正确 - 已还原。
【解决方案2】:

@Alnitak 的回答很聪明,帮助我消除了我创建的一个 hack,我在其中有点人为地解决了一个承诺——不管基本结果如何——以便我可以使用“何时”来批量处理多个请求并使用'done' 继续进行,不管他们的成功/失败。

我正在“回答”Alnitak 的回答,希望为他的支持任意数量的基本承诺的建议提供另一种用途。

var asyncFunc, entity, entities, $deferred, $deferreds;
// ...
foreach (entity in entities) {
    $deferred = $.Deferred();
    $deferreds.push($deferred);
    asyncFunc(entity).done(...).fail(...).always($deferred.resolve);
}
// ...
$.when.apply($, $deferreds).done(...)

这是伪 JavaScript,但它应该传达这种方法。对于一些任意大小的实体集,为每个实体创建一个延迟($deferred)并将其推送到一个数组($deferreds)上,进行异步调用,根据需要添加完成/失败,但始终包含解决此问题的“始终”实体的 $deferred。 NB 'always' 接收延迟的解析函数而不是它的调用。

'when' 将 $deferreds 数组转换为 'when' 的参数列表,并且由于保证了这组 deferreds 可以解决(感谢 always),现在可以定义一个 'done' 将是在所有异步调用完成后调用,无论这些调用是否成功/失败。

【讨论】:

    【解决方案3】:

    我最近制作了一个可能会有所帮助的插件。我称之为$.whenAll

    此扩展将所有成功和失败都视为进度事件。 在所有的promise都完成后,全局promise就解决了 如果没有错误。否则全局承诺将被拒绝。

    $.whenAll - https://gist.github.com/4341799 (tests)

    示例用法:

    $.whenAll($.getJSON('foo'), $.getJSON('bar'))
      .then(
        doneCallback
        ,failcallback
        // progress callback
        // the only problem is $.ajax.done/fail states call their callbacks 
        // with params in different locations (except for state)
        ,function(data, state, jqXhr) {
          if (state == 'success') {
            // do happy stuff
          }
          else { // error (fail)
            // `data` is actually the jqXhr object for failed requests
            // `jqXhr` is the text of the error "Not Found" in this example
          }
        }
      )
    ;
    

    【讨论】:

    • 虽然它已经很老了,但它做得非常好,非常需要。谢谢!干得好,我特别感谢测试。
    【解决方案4】:

    我的实现:

    插件代码:

    jQuery.whenAll = function (deferreds) {
            var lastResolved = 0;
    
            var wrappedDeferreds = [];
    
            for (var i = 0; i < deferreds.length; i++) {
                wrappedDeferreds.push(jQuery.Deferred());
    
                deferreds[i].always(function() {
                    wrappedDeferreds[lastResolved++].resolve(arguments);
                });
            }
    
            return jQuery.when.apply(jQuery, wrappedDeferreds).promise();
        };
    

    要使用它:

    jQuery.whenAll([jQuery.get('/your-resource'), jQuery.get('/your-resource')])
       .done(
           function(result1, result2) {
               console.log(result1[1]);
               console.log(result2[1]);
           });
    

    看看小提琴: http://jsfiddle.net/LeoJH/VMQ3F/

    【讨论】:

      【解决方案5】:

      这是一个 jQuery 插件,我通过修改 $.when() 的实际核心代码来使用您的语义。想要一个更好的名字就叫$.myWhen():

      (function($) {
        $.myWhen = function( subordinate /* , ..., subordinateN */ ) {
          var i = 0,
            responseValues = Array.prototype.slice.call( arguments ),
            length = responseValues.length,
      
            // the count of uncompleted subordinates
            remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
      
            // the master Deferred. If responseValues consist of only a single Deferred, just use that.
            deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
      
            // Update function for all resolve, reject and progress values
            updateFunc = function( i, contexts, values ) {
              return function( value ) {
                contexts[ i ] = this;
                values[ i ] = arguments.length > 1 ? Array.prototype.slice.call( arguments ) : value;
                if( values === progressValues ) {
                  deferred.notifyWith( contexts, values );
                } else if ( !( --remaining ) ) {
                  deferred.resolveWith( contexts, values );
                }
              };
            },
      
            progressValues, progressContexts, responseContexts;
      
          // add listeners to Deferred subordinates; treat others as resolved
          if ( length > 1 ) {
            progressValues = new Array( length );
            progressContexts = new Array( length );
            responseContexts = new Array( length );
            for ( ; i < length; i++ ) {
              if ( responseValues[ i ] && jQuery.isFunction( responseValues[ i ].promise ) ) {
                responseValues[ i ].promise()
                  .always( updateFunc( i, responseContexts, responseValues ) )
                  .progress( updateFunc( i, progressContexts, progressValues ) );
              } else {
                --remaining;
              }
            }
          }
      
          // if we're not waiting on anything, resolve the master
          if ( !remaining ) {
            deferred.resolveWith( responseContexts, responseValues );
          }
      
          return deferred.promise();
        };
      })(jQuery);
      

      只需将此代码放在您加载 jQuery 的位置之后,$.myWhen() 函数将与$.when() 一起可用。除了语义之外,其他一切都是 100% 完全相同的。

      【讨论】:

        【解决方案6】:

        对 Leo Hernandez 的解决方案的改进,适用于更一般的用例,这些用例不仅仅涉及从服务器获取资源,例如可以包括由用户交互触发的事件或异步 jQuery UI 调用(例如 slideUp() 和 slideDown( ))。请参阅https://jsfiddle.net/1trucdn3/ 以了解增强的用例。

        $.whenAll = function (deferreds) {
            var lastResolved = 0;
            var wrappedDeferreds = [];
        
            for (var i = 0; i < deferreds.length; i++) {
                wrappedDeferreds.push($.Deferred());
                if (deferreds[i] && deferreds[i].always) {
                    deferreds[i].always(wrappedDeferreds[lastResolved++].resolve);
                } else {
                    wrappedDeferreds[lastResolved++].resolve(deferreds[i]);
                }
            }
        
            return $.when.apply($, wrappedDeferreds).promise();
        };
        

        改进允许我们将非延迟值传递到数组参数中。这是你可以用 $.when() 做的事情。此外,我清理了您在回调函数中获得的输出,以便与原始 $.when() 方法的工作方式更加一致,以防您只想获取结果而不管状态如何。 Leo 的解决方案会将整个延迟对象作为结果传递,然后您需要对其进行挖掘以找到所需的信息。

        $.whenAll([1, $.Deferred().resolve("Good"), $.Deferred().reject("Bad")])
            .done(function (result1, result2, result3) {
                // result1 -> 1
                // result2 -> "Good"
                // result3 -> "Bad"
            });
        

        【讨论】:

          【解决方案7】:

          @Alnitak 和@DazWilkin 的答案很棒!但我个人更喜欢函数式风格,所以这里是任意数量的 promise 的函数式版本:

          var entities;
          // ...
          var deferreds = entities.map(function() {
              var deferred = $.Deferred();
              asyncFunc(this).done(...).fail(...).always(deferred.resolve);
              return deferred;
          }
          // ...
          $.when.apply($, deferreds).done(...)
          

          与@DazWilkin 的回答相比,我使用map 函数而不是foreach

          【讨论】:

            【解决方案8】:

            我找到了一个解决方案,其中我有 2 个请求,即使其中一个请求失败,也能够访问单个成功:

                    $.when
                    (
                        $.getJSON(...).then(function (results)
                        {
                            console.log('SUCCESS REQUEST 1 BY ITSELF', results);
                        }),
                        $.getJSON(...).then(function (results)
                        {
                            console.log('SUCCESS REQUEST 2 BY ITSELF', results);
                        })
                    ).then
                    (
                        function (results1, results2)
                        {
                            console.log('BOTH REQUESTS SUCCESSFUL...');
                            console.log('results1', results1);
                            console.log('results2', results2);
                        },
                        function (error1, error2)
                        {
                            console.log('AT LEAST 1 REQUEST FAILED...');
                            console.log('error1', error1);
                            console.log('error2', error2);                  
                        }
                    );
            

            【讨论】:

              猜你喜欢
              • 2012-09-05
              • 2019-04-17
              • 1970-01-01
              • 2015-09-27
              • 2015-09-04
              • 2014-05-03
              • 1970-01-01
              • 2021-08-13
              • 2015-12-29
              相关资源
              最近更新 更多