jQuery.when - 当所有延迟不再“未解决”(解决或拒绝)时回调?

时间:2011-04-28 20:33:08

标签: javascript jquery rest jquery-deferred

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

该方法将

  1. 在所有延期解决后立即解决其主延期,或
  2. 拒绝其主人在一个被延期的人被拒绝后立即推迟。
  3. 如果解析了主延迟(即所有Deferreds解析),则传递给传递给jQuery.when的所有Deferred的已解析值。例如,当Deferreds是jQuery.ajax()请求时,参数将是请求的jqXHR对象,按照它们在参数列表中给出的顺序:

    $.when( $.getJSON('foo'), $.getJSON('bar') ).done(function(foo, bar) {
    
        // foo & bar are jqXHR objects for the requests
    
    });
    

    在其中一个Deferreds被拒绝的多个Deferreds案例中,jQuery.when立即激活其主Deferred的失败回调,即使某些Deferreds在此时仍未解决:

    $.when( $.getJSON('foo'), $.getJSON('bar') ).fail(function(req) {
    
        // req is the jqXHR object for one of the failed requests
    
    });
    

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

8 个答案:

答案 0 :(得分:44)

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

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

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

$.when(j1, j2).done( 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() { ... }块中。

答案 1 :(得分:11)

@Alnitak的回答很聪明,帮助我抹去了我创造的一个黑客,我在某种程度上人为地解决了一个承诺 - 无论其根本结果如何 - 以便我可以使用'when'来批量处理多个请求并使用无论成功/失败,都要“完成”。

我正在“回答”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),进行异步调用,根据需要添加done / fail但总是包含一个解决这个问题的'always'实体的延期。 NB 'always'接收延迟的解析函数而不是它的调用。

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

答案 2 :(得分:9)

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

  

此扩展程序将所有成功和失败视为进度事件。   在所有承诺完成后,全球承诺得以解决   如果没有错误。否则,全球承诺将被拒绝。

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

样本用法:

$.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
      }
    }
  )
;

答案 3 :(得分:6)

我的实施:

插件代码:

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/

答案 4 :(得分: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%完全相同。

答案 5 :(得分: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"
    });

答案 6 :(得分:0)

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

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

答案 7 :(得分: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);                  
            }
        );