39

当多个 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。我怎样才能做到这一点?

4

8 回答 8

45

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

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() { ... }块中。

于 2011-04-28T21:25:04.370 回答
11

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

我正在“回答”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。注意“总是”接收延迟的解析函数而不是它的调用。

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

于 2012-08-02T21:02:40.263 回答
10

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

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

$.whenAll - https://gist.github.com/4341799测试

示例用法:

$.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
      }
    }
  )
;
于 2013-01-30T12:58:54.303 回答
9

我的实现:

插件代码:

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/

于 2014-02-26T19:08:05.200 回答
3

这是一个 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% 完全相同的。

于 2012-08-21T20:24:51.200 回答
0

对 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"
    });
于 2015-05-21T18:00:55.923 回答
0

@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 答案相比,我使用mapfunction 而不是foreach.

于 2015-06-15T21:31:29.420 回答
0

我找到了一个解决方案,其中我有 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);                  
            }
        );
于 2016-01-26T16:40:46.180 回答