jQuery Deferred的、$.when()和fail()回调函数参数

24

当使用$.when()时,如果其中一个延迟操作不成功,我会得到意外的结果。

考虑这段JavaScript代码,它创建了2个延迟(deferred)对象。第一个执行成功,而第二个执行失败。

var f1 = function() {
    return $.Deferred(function(dfd) {
        dfd.resolve('123 from f1');
    }).promise();
};

var f2 = function() {
    return $.Deferred(function(dfd) {
        dfd.reject('456 from f2');
    }).promise();
};

$.when(f1(), f2())
    .then(function(f1Val, f2Val) {
        alert('success! f1, f2: ' + JSON.stringify([f1Val, f2Val]));
    })
    .fail(function(f1Val, f2Val) {
        alert('fail!    f1, f2: ' + JSON.stringify([f1Val, f2Val]));
    });

请自行运行代码: http://jsfiddle.net/r2d3j/2/

我得到了fail! f1, f2: ["456 from f2", null]

问题在于,在.fail()回调中,与f2()拒绝一起传递的值被路由到第一个参数中,而我期望的是f1Value。这意味着我实际上没有办法知道哪个延迟对象实际上发布了那个reject(),我也不知道该故障数据属于哪个操作。

我本来希望.fail()会得到参数null,'456 from f2',因为第一个延迟没有失败。或者我在这里使用延迟的方式不正确吗?

如果回调中的参数顺序不被尊重,我怎样才能知道哪些延迟失败,并且哪些拒绝参数属于哪个失败的延迟?

4个回答

27

$.when()会在任何一个参数失败时立即执行失败的回调函数(传递给then()的第二个参数)。这是有意为之的。引用文档中的话:

http://api.jquery.com/jQuery.when/

在多个Deferred对象的情况下,如果其中一个被拒绝了,jQuery.when()立即为其主Deferred触发failCallbacks。请注意,此时仍可能有一些Deferred对象未解决。如果您需要针对此情况执行其他处理(例如取消任何未完成的ajax请求),则可以在闭包中保留对底层jqXHR对象的引用,并在failCallback中检查/取消它们。 实际上没有内置的方法可以获取等待所有异步操作完成的回调,无论成功或失败。因此,我为您构建了一个$.whenAll() :) 它始终等待所有异步操作完成,无论成功或失败:

http://jsfiddle.net/InfinitiesLoop/yQsYK/51/

$.whenAll(a, b, c)
    .then( callbackUponAllResolvedOrRejected );

1
你说“将立即执行then()回调函数”,但你引用的语句说“立即触发failCallbacks”- 那到底是哪个? \-: - hippietrail
2
@hippietrail 这是两者兼备的情况。当延迟对象"resolved"或"rejected"时,then()方法都会触发。由于这里是属于被拒绝的情况,因此它会被触发。 - InfinitiesLoop
3
不,它不会触发“then”回调函数,而是会触发失败回调函数(在技术上可以是传递给“then”的第二个回调函数,但无论如何,它都不同于大多数读者认为的“then”回调函数,即“成功”回调函数)。 - hasen
这个答案仍然有帮助,近5年后!谢谢! - adam
@InfinitiesLoop 这里有任何更新吗?还是认为最佳答案已经提供了解决方案? - shamaleyte
你可以使用 Promise.allSettled,检查我的答案。 - João Pimentel Ferreira

4

在内部,“拒绝”和“失败”路径由两个完全独立的队列处理,因此它不会按你期望的方式工作。

为了知道哪个最初的Deferred从“when()”组失败,您可以让它们在对象文字或其他内容的一部分中随“。reject()”调用一起传递自己。


嗯,不完全是我设计的方式,但我想这样做也让我的结果有意义。 - Alex Wayne
@Mootoo自从回答这个问题以来,jQuery已经发生了很大的变化。现在它使用标准的Promise语义。 - Pointy

0

我曾面对过这个问题,通过使用 .always 回调并检查我的延迟对象数组来处理它。由于我有未知数量的 ajax 调用,所以我必须执行以下操作:

// array of ajax deletes
var deletes = [];
$checkboxes.each(function () {
    deletes.push(deleteFile(this));
});

$.when.apply($, deletes)
  .always(function () {
      // unfortunately .fail shortcircuits and returns the first fail,
      // so we have to loop the deferred objects and see what happened.

      $.each(deletes, function () {
          this.done(function () {
              console.log("done");
          }).fail(function () {
              console.log("fail");
          });
      });
  });

deleteFile方法返回一个Promise,其中包含.done或.fail回调函数。

这使您可以在所有延迟完成后采取行动。在我的情况下,我将显示删除文件错误摘要。

我刚刚尝试了这个,不幸的是,我不得不设置一个间隔计时器来检查在延迟对象的$.each之后它们是否都真正完成了。这似乎很奇怪和违反直觉。

仍在努力理解这些延迟对象!


如果任何延迟执行失败,始终也会“短路”。 - Chloraphil

0

虽然这是一个很老的问题,但现在你可以使用Promise.allSettled来等待所有问题解决,因为$.ajax处理标准承诺。

自jQuery 1.5以来,由$.ajax()返回的jqXHR对象实现了Promise接口,使它们具有Promise的所有属性、方法和行为。

因此,您可以使用

Promise.allSettled([$.ajax(), $.ajax()])
  .then((res) => {
    if (res[0].status === 'fulfilled') {
      console.log(res[0].value)
      // do something
    } else {
      console.error('res1 unavailable')
    }

    if (res[1].status === 'fulfilled') {
      console.log(res[1].value)
      // do something
    } else {
      console.error('res2 unavailable')
    }
  })

除了IE 11不支持allSettled之外。 - Ian Kemp
1
@IanKemp 很有趣,我不知道,无论如何,我不需要IE,但这是一个很好的观点。 - João Pimentel Ferreira
1
正是我所需要的!谢谢。 - ph0enix

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接