承诺:即使.catch()被执行,.done()总是会被执行吗?

7

我的Promise问题

我刚接触Promises,并且一直在阅读Q文档,其中写道:

当你到达Promise链的末端时,应该返回最后一个Promise或结束该链。

我已经按照Q.Promise的方式在我的代码中定义了一个Promise,并添加了以下console.log以记录执行跟踪:

function foo(){
   return Q.Promise(function(resolve, reject) {

    doSomething()
    .then(function() {
      console.log('1');
      return doSomething1();
    })
    .then(function() {
      console.log('2');
      return doSomething2();
    })
    .then(function() {
      console.log('3');
      return doSomething3();
    })
    .catch(function(err) {
      console.log('catch!!');
      reject(err);
    })
    .done(function() {
      console.log('done!!');
      resolve();
    });

  });
}

如果每个doSomethingN()都正确执行,一切按预期运行,我得到了期望的跟踪:

1
2
3
done!!

但是如果doSomethingN()中的任何一个失败:

foo()仍能正常工作,因为错误函数回调会在出现reject(err)时运行:

foo().then(function() { /* */ }, function(err) { /* 这里运行! */ });

doSomething1()失败时,我会得到以下跟踪信息:

1
catch!!
done!!

我的问题

我最初的想法是:

好的,让我们同时处理 .done().catch() 方法中的链式 成功失败。如果一切顺利,.done() 的回调函数将会被执行,承诺将得到解决。如果在任何时候出现错误,则会执行 .catch() 的回调函数并拒绝承诺——因此,done() 将不会被执行。

我认为我对 .done() 的工作原理有所误解……因为通过查看我的日志跟踪,我意识到 .done() 似乎总是被执行——无论是否出现错误并执行了 .catch()——这不是我预期的结果。

因此,在那之后,我移除了 .done() 的回调函数,现在 foo():

  • 如果在链条执行过程中有一个 error ,它可以正常工作
  • 如果一切正常工作,它就不能正常工作

我应该重新考虑什么?我如何使它工作?


2
首先,避免Promise构造函数反模式! - Bergi
1
你的问题不在于 done,而在于 catch。捕获错误意味着你已经处理了它,并且从 .catch(…) 返回的 Promise - 也就是你附加 .done() 的那个 Promise - 将会被实现! - Bergi
@Bergi,恐怕我不理解你在第二条评论中试图解释的内容...那个.catch()有什么问题吗? - charliebrownie
1
它返回一个承诺,最终将被实现,这就是为什么即使出现错误,您的“完成”也会运行的原因。请查看“链接的承诺未传递拒绝”的内容(https://dev59.com/f2Qo5IYBdhLWcg3wDLpj) - Bergi
@Bergi,你提供的关于这个主题的链接非常有用! - charliebrownie
3个回答

5

catch(cb)只是then(null, cb)的别名,并且你实际上已经在catch中修复了一个错误,因此流程自然地转向了done中的success结果。

如果你只想在catch装饰错误,你应该在之后重新抛出错误,例如适当的传递可能如下所示:

catch(function (err) {
   console.log(err);
   throw err;
});

您的例子并不是很有意义。当返回一个promise时,您永远不应该使用done。如果您想要使用内部创建的一系列promises来解决初始化的promise,那么您只需要这样解决它:

resolve(doSomething()
  .then(function() {
    console.log('1');
    return doSomething1();
  })
  ....
  .then(function() {
    console.log('N');
    return doSomethingN();
  }));

不需要进行内部错误处理,将其留给您返回的promise的使用者。

另一个要点是,如果在创建新的promise时,您知道它将与其他promise一起解决,那么没有逻辑上的理由创建这样的promise,只需重复使用您计划解决的promise即可。这种错误也被称为延迟反模式


非常好。我唯一的问题是:用resolve()包装链有什么区别? - charliebrownie
1
区别就像是 resolve(promise)promise.done(resolve, reject) 之间的区别一样。两者都会做完全相同的事情,但第一个更加直观 :) - Mariusz Nowak
仍然创建新的 Promise,就像你的例子一样,没有意义,你应该直接返回那个链式 Promise。 - Mariusz Nowak
是的,绝对,那个链应该按顺序返回,以便在调用 foo 时得到那个 Promise。谢谢! :-) - charliebrownie

3
你应该考虑这样做:
function foo() {
  // Calling .then() on a promise still return a promise.
  // You don't need Q.Promise here
  return doSomething()
    .then(function(doSomethingResult) {
      console.log('1');
      return doSomething1();
    })
    .then(function(doSomething1Result) {
      console.log('2');
      return doSomething2();
    })
    .then(function(doSomething2Result) {
      console.log('3');
      return doSomething3();
    });
}



foo()
  .then(function(fooResult) {
    console.log(fooResult); // fooResult should be what is returned by doSomething3()
  })
  .catch(function(err) {
    console.error(err); // Can be thrown by any 
  })
  .done(function() {
    console.log('I am always executed! error or success');
  });

如果你想返回一个Promise,在大多数情况下使用catch并没有太多意义(除非你想恢复潜在的错误)。在返回Promise的方法中使用done从来都没有意义。你最好在链的最后使用这些方法。

请注意,doSomethingX()可以返回值或Promise,它们的效果是相同的。


很好的指出并且解释得非常清楚,@SebastienLorber!我有一个最后一个问题是关于何时使用deferreds或者new Promises:所以,我只应该在我想要返回一个promise的函数内部使用这些(其中之一)来进行“设置”。在这种情况下,在foo()内部,您不需要deferredsnew Promises,因为每个doSomethingN()中已经完成了这个过程(它们已经返回了一个promise),并且您可以通过.then()直接获取这些...我是对的吗?我想我明白了这是如何工作的...! - charliebrownie
如果任何一个 doSomethingN() 函数抛出了 reject,它会传播并在 foo().catch() 处被捕获,对吗 @SebastienLorber? - charliebrownie
1
另外,如果你的 doSomethingX() 操作不需要使用之前的结果来执行,那么你可以并行启动它们(使用 Q.all([smth1Promise,smth2Promise,smth3Promise]).spread(res1,res2,res3)... - Sebastien Lorber

0

你可以通过在最后一个then回调中解决promise来使其工作。

function foo(){
    return doSomething()
    .then(function() {
      console.log('1');
      return doSomething1();
    })
    .then(function() {
      console.log('2');
      return doSomething2();
    })
    .then(function() {
      console.log('3');
      return doSomething3();
    })
}

考虑使用bluebird来处理Promise。与其他Promise库相比,它具有许多有用的功能。你可能会发现开始使用它有些困难,但一旦掌握了它,你就会爱上它。


当所有doSomethingN的调用都解决了,它还是不工作吗? - Kunal Kapadia
我在这里同意@Bergi的看法。 - charliebrownie
1
@KunalKapadia:不,它按照你的意图“工作”,但你的答案仍然表现出了一种不良实践。 - Bergi
是的@KunalKapadia,我正在寻找正确的解决方案。我之前已经考虑过你的解决方案,但我因为Bergi所说的而放弃了它。 - charliebrownie
1
@Bergi 我同意你的观点。我已经从我的代码中消除了反模式。 - Kunal Kapadia

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