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

IT技术 javascript jquery rest jquery-deferred
2021-02-07 10:18:29

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

该方法将要么

  1. 一旦所有延迟解决,就解决其主延迟,或
  2. 一旦其中一个 Deferred 被拒绝,就拒绝其主 Deferred。

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

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

    // foo & bar are jqXHR objects for the requests

});

在多个延迟的情况下,其中一个延迟被拒绝,jQuery.when 立即触发其主延迟的失败回调,即使此时某些延迟可能仍未解决:

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

6个回答

我认为,要做到这一点最简单的方法就是保持辅助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 方法的Promise中,该方法对已解决和被拒绝的Promise均调用。

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

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

@Alnitak 的回答很聪明,并帮助我消除了我创建的一个 hack,在该 hack 中我有点人为地解决了一个Promise - 无论潜在的结果如何 - 为了我可以使用“when”来批量处理多个请求并使用“done”无论成功/失败,都继续前进。

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

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' 的参数列表,并且由于这组延迟可以保证解析(感谢 always),现在可以定义一个将被调用一次的 'done'无论这些是否成功/失败,异步调用都会完成。

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

此扩展将所有成功和失败视为进度事件。在所有的 Promise 完成后,如果没有错误,全局 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
      }
    }
  )
;
虽然这是非常古老的,但做得非常好,非常需要。谢谢!干得好,我特别欣赏这些测试。
2021-03-25 10:18:29

我的实现:

插件代码:

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/

这是我通过修改实际核心代码$.when()来使用您的语义制作的 jQuery 插件想要一个更好的名字,它被称为$.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% 完全相同的。