关于javascript:什么时候.then(成功,失败)被认为是承诺的反模式?

When is .then(success, fail) considered an antipattern for promises?

我看过《蓝鸟承诺》常见问题解答,其中提到.then(success, fail)是一个反模式。我不太明白它对尝试和捕获的解释。这下面有什么问题?

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

这个例子似乎表明了以下正确的方法。

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

有什么区别?


What's the difference?

.then()调用将返回一个承诺,如果回调抛出错误,该承诺将被拒绝。这意味着,当您的成功logger失败时,错误将传递给下面的.catch()回调,但不会传递给与success一起进行的fail回调。

以下是控制流程图:

control flow diagram of then with two argumentscontrol flow diagram of then catch chain

要用同步代码表示它:

1
2
3
4
5
6
7
8
9
10
// 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用于(推荐阅读!).

1
2
3
4
5
6
7
// some_promise_call().then(logger.log).catch(logger.log)
try {
    var results = some_call();
    logger.log(results);
} catch(e) {
    logger.log(e);
}

catch记录器还将处理来自成功记录器调用的异常。

两者之间的差异是如此之大。

I don't quite understand its explanation as for the try and catch

其理由是,通常您希望在处理的每个步骤中捕获错误,并且不应在链中使用它。期望是您只有一个处理所有错误的最终处理程序,而当您使用"反模式"时,一些随后回调中的错误不会被处理。

然而,这个模式实际上非常有用:当您想要处理在这个步骤中发生的错误时,并且当没有发生错误时(即当错误不可恢复时),您想要做一些完全不同的事情。请注意,这是在分支您的控制流。当然,这有时是需要的。

What's wrong with this the following?

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

你必须重复你的回拨。你更想要

1
2
3
4
5
6
7
some_promise_call()
   .catch(function(e) {
       return e; // it's OK, we'll just log it
   })
   .done(function(res) {
       logger.log(res);
   });

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


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

但是我相信你链接到的文本的要点是,与回调相比,then在链接一系列异步步骤的能力上最有用,而且当你真正这样做时,then的2参数形式由于上述原因,并不像预期的那样微妙。当使用中链时,这是特别违反直觉的。

作为一个做了很多复杂的异步工作并碰到这样的角落的人,我真的建议避免这种反模式并使用单独的处理程序方法。


通过观察两者的优点和缺点,我们可以对哪一种情况适合做一个经过计算的猜测。这是实现承诺的两种主要方法。两者都有正负之分

Catch Approach

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

优势

  • 所有错误都由一个catch块处理。
  • 甚至在then块中捕获任何异常。
  • 链接多个成功回调
  • 缺点

  • 在链接的情况下,很难显示不同的错误消息。
  • Success/Error Approach

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

    优势

  • 您将得到细粒度的错误控制。
  • 您可以为各种类型的错误(如数据库错误、500错误等)提供常见的错误处理功能。
  • 歧化剂

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

  • 简单解释:

    在ES2018中

    When the catch method is called with argument onRejected, the
    following steps are taken:

  • Let promise be the this value.
  • Return ? Invoke(promise,"then", ? undefined, onRejected ?).
  • 这意味着:

    1
    promise.then(f1).catch(f2)

    等于

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

    不是用言语,而是好榜样。以下代码(如果第一个承诺已解决):

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

    与以下内容相同:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Promise.resolve()
    .catch
    (
      err => console.log('This error is caught:', err)
    )
    .then
    (
      () => { throw new Error('Error occurs'); }
    )

    但由于第一个承诺被拒绝,这是不相同的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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'); }
    )