.then(成功,失败)什么时候被视为承诺的反模式?


188

我看了蓝鸟诺言常见问题解答,其中提到这.then(success, fail)是一种反模式。我对试捕一无所知。以下是什么问题?

some_promise_call()
.then(function(res) { logger.log(res) }, function(err) { logger.log(err) })

该示例似乎建议以下是正确的方法。

some_promise_call()
.then(function(res) { logger.log(res) })
.catch(function(err) { logger.log(err) })

有什么不同?


1
then().catch()更具可读性,因为您无需查找逗号并调查此回调是成功还是失败分支。
Krzysztof Safjanowski14年

7
@KevinB:有很大的不同,请查看答案
Bergi 2014年

12
@KrzysztofSafjanowski-被“看起来更好”的论点所破坏。完全错误!
安德烈·波波夫

6
注意:使用时.catch,您不知道是哪个步骤导致了问题-在then承诺链的最后一个或其他位置。因此它确实有其自身的缺点。
vitaly-t 2015年

2
我总是在promise .then()参数中添加函数名称以使其可读,即some_promise_call() .then(function fulfilled(res) { logger.log(res) }, function rejected(err) { logger.log(err) })
Shane Rowatt

Answers:


215

有什么不同?

.then()调用将返回一个promise,以防回调引发错误。这意味着当成功logger失败时,错误将传递给以下.catch()回调,但不会fail传递给与一起的回调success

这是一个控制流程图

然后带有两个参数的控制流程图 然后捕获链的控制流程图

要用同步代码表达它:

// some_promise_call().then(logger.log, logger.log)
then: {
    try {
        var results = some_call();
    } catch(e) {
        logger.log(e);
        break then;
    } // else
        logger.log(results);
}

第二个log(类似于的第一个参数.then())仅在没有异常发生的情况下执行。带标签的块和break语句有点奇怪,这实际上是python的try-except-else功能(建议阅读!)。

// some_promise_call().then(logger.log).catch(logger.log)
try {
    var results = some_call();
    logger.log(results);
} catch(e) {
    logger.log(e);
}

catch记录仪也将处理来自成功记录通话例外。

差异很大。

我对试捕不甚了解

论据是通常您希望在处理的每个步骤中都捕获错误,并且您不应该在链中使用它。期望您只有一个处理所有错误的最终处理程序-而当您使用“反模式”时,将不处理某些then-callbacks中的错误。

但是,此模式实际上非常有用:当您要处理恰好在此步骤中发生的错误,并且想要在没有错误发生时(即当错误不可恢复时)执行完全不同的操作请注意,这正在分支您的控制流。当然,有时是需要的。


以下是什么问题?

some_promise_call()
.then(function(res) { logger.log(res) }, function(err) { logger.log(err) })

您必须重复回调。你宁愿

some_promise_call()
   .catch(function(e) {
       return e; // it's OK, we'll just log it
   })
   .done(function(res) {
       logger.log(res);
   });

您也可以考虑.finally()为此使用。


7
这是几天以来我读过的最有用的解释(而且我读了很多)。我无法解释我有多感恩!:)我认为您应该特别强调两者之间的差异,即使在成功函数内部,它们.catch也将捕获错误。.我个人认为,这非常错误,因为您最终遇到一个错误输入点,该错误输入点可能会导致多个错误。多个动作,但这是我的问题。无论如何-感谢您提供信息!您没有愿意分享的在线交流工具,所以我想再问几件事吗?:P
Andrey Popov

2
我希望这里能给您更多的支持。绝对是Promise该站点上重要机械师的最佳解释之一。
帕特里克·罗伯茨

2
.done()不是标准的一部分,对吗?至少MDN没有列出该方法。这将是有帮助的。
ygoe '19

1
@ygoe确实。done是Bluebird的东西,基本上已被then+未处理的拒绝检测所弃用。
Bergi

1
只是色盲的注释:图表没有意义:)
Benny K

37

两者并不完全相同。不同之处在于,第一个示例不会捕获success处理程序中引发的异常。因此,如果您的方法通常只返回已解析的Promise,则需要尾随catch处理程序(或另一个then带有空success参数的处理程序)。当然,您的then处理程序可能不会做任何可能会失败的事情,在这种情况下,使用一个2参数then可能会很好。

但是我相信您链接到的文本的要点是,then与回调相比,它链接一系列异步步骤的功能最有用,并且当您实际执行此操作时,2参数形式的then巧妙表现并不如预期的那样,由于上述原因。当使用中间链时,这尤其违反直觉。

作为做了很多复杂的异步工作并碰到这样的困境的人,我确实建议避免使用这种反模式,而应该使用单独的处理程序方法,这比我想承认的要多。


18

通过查看两者的优缺点,我们可以对哪种情况适合进行计算得出猜测。这是实现承诺的两种主要方法。两者都有其优缺点

渔获法

some_promise_call()
.then(function(res) { logger.log(res) })
.catch(function(err) { logger.log(err) })

优点

  1. 所有错误均由一个catch块处理。
  2. 甚至在then块中捕获任何异常。
  3. 链接多个成功回调

缺点

  1. 在链接的情况下,很难显示不同的错误消息。

成功/错误方法

some_promise_call()
.then(function success(res) { logger.log(res) },
      function error(err) { logger.log(err) })

优点

  1. 您可以获得细粒度的错误控制。
  2. 您可以针对各种错误类别(例如db错误,500错误等)具有通用的错误处理功能。

破坏

  1. catch如果您希望处理成功回调引发的错误,则仍然需要另一个

对于只需要使用日志文件调试生产问题的人,我更喜欢“成功/错误方法”,因为它可以创建因果错误链,该因果错误链可以记录在应用程序的出口边界。
Shane Rowatt

题。可以说我做了一个异步调用,该调用执行以下操作之一:1)成功返回(2xx状态码),2)未成功返回(4xx或5xx代码)但本身未被拒绝,3)或根本不返回(互联网连接已断开)。对于情况#1,将命中.then中的成功回调。对于案例2,点击.then中的错误回调。对于情况3,将调用.catch。这是正确的分析,对不对?情况2是最棘手的,从技术上讲4xx或5xx并不是拒绝,它仍然可以成功返回。因此,我们需要在.then内处理它。....我的理解正确吗?
本杰明·霍夫曼

“对于情况2,命中.then中的错误回调。对于情况3,将调用.catch。这是正确的分析,对吗?” -提取方式就是这样
-aWebDeveloper

2

简单说明一下:

在ES2018中

当使用参数onRejected调用catch方法时,将执行以下步骤:

  1. 让诺就是这个价值。
  2. 返回?调用(承诺,“然后”,«未定义,onRejected»)。

这意味着:

promise.then(f1).catch(f2)

等于

promise.then(f1).then(undefiend, f2)

1

使用.then().catch()使您可以启用完成工作流程所需的Promise Chaining。您可能需要从数据库中读取一些信息,然后将其传递给异步API,然后才能处理响应。您可能需要将响应推回数据库。用您的概念来处理所有这些工作流是可行的,但很难管理。更好的解决方案是then().then().then().then().catch()一次捕获所有错误,并让您保持代码的可维护性


0

使用then()catch()帮助将成功和失败处理程序按承诺进行链接。catch()履行诺言返回的承诺then()。它处理

  1. 如果诺言被拒绝。参见图片中的#3
  2. 如果then()的成功处理程序中发生错误,则在下面的第4到7行之间。请参阅图片中的#2.a(“失败回调” then()无法对此进行处理。)
  3. 如果then()的故障处理程序中发生错误,则在下面的第8行。参见图片中的#3.b。

1. let promiseRef: Promise = this. aTimetakingTask (false); 2. promiseRef 3. .then( 4. (result) => { 5. /* successfully, resolved promise. 6. Work on data here */ 7. }, 8. (error) => console.log(error) 9. ) 10. .catch( (e) => { 11. /* successfully, resolved promise. 12. Work on data here */ 13. });

在此处输入图片说明

注意:很多时候,如果catch()已经编写了失败处理程序,则可能未定义。编辑:reject()导致调用catch()只有在错误处理程序then()没有定义的。注意图片中的#3 catch()。未定义第8行和第9行中的处理程序时将调用该方法。

这是有道理的,因为then()如果回调正在处理的话,promise返回的promise 没有错误。


从数字3到catch回调的箭头似乎不正确。
Bergi

谢谢!如果在then()中定义了错误回调,则不会调用它(代码段中的第8行和第9行)。#3调用两个箭头之一。这是有道理的,因为如果回调正在处理的话,那么then()返回的promise没有错误。编辑答案!
VenCKi

-1

代替言语,好榜样。以下代码(如果第一个承诺得到解决):

Promise.resolve()
.then
(
  () => { throw new Error('Error occurs'); },
  err => console.log('This error is caught:', err)
);

等同于:

Promise.resolve()
.catch
(
  err => console.log('This error is caught:', err)
)
.then
(
  () => { throw new Error('Error occurs'); }
)

但是在拒绝了第一个承诺的情况下,这并不相同:

Promise.reject()
.then
(
  () => { throw new Error('Error occurs'); },
  err => console.log('This error is caught:', err)
);

Promise.reject()
.catch
(
  err => console.log('This error is caught:', err)
)
.then
(
  () => { throw new Error('Error occurs'); }
)

4
这没有道理,您可以删除此答案吗?这会产生误导,并分散正确答案的注意力。
安迪·雷

@AndyRay,这在实际应用中没有任何意义,但了解诺言的工作是有道理的。
ktretyak '18
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.