【问题标题】:How promise chains reference the next error handler承诺链如何引用下一个错误处理程序
【发布时间】:2018-11-25 19:48:12
【问题描述】:

我想知道是否有人知道承诺链如何引用下一个错误处理程序 - 例如:

  const p = new Promise(resolve => resolve(5))
   .then(v => 5*v)
   .then(v => { throw 'foo' });

  p.then(v => v/4)
   .then(v => v+3)
   .catch(e => console.error('first catch:', e));

  p.then(v => v/4)
   .then(v => v+3)
   .catch(e => console.error('second catch:', e));

如果你运行它,你会得到:

first catch: foo
second catch: foo

据我所知,每次调用 Promise.prototype.then 时,都会创建并返回一个新的 Promise。为了保证能够为每个链找到 下一个错误处理程序,我能想到的唯一方法是拥有一个对子项的引用数组。因此,如果一个 Promise 被拒绝,它将遍历所有子节点,并在每个子链中找到最近的拒绝处理程序。

有人知道这是怎么实现的吗?

【问题讨论】:

  • const p 是一个被拒绝的承诺——你知道吗?重新阅读问题,我想你可能会这样做 - 抱歉
  • 您要问的是实现细节,这取决于哪个浏览器(对于本机承诺)或哪个库(对于第 3 方承诺)。不过,一般来说,promise 需要跟踪其直系子级,以便在解决或拒绝时通知它们。他们不需要跟踪几个分离度的处理程序,因为每个.then.catch 都会创建一个带有自己“孩子”的新承诺。

标签: javascript node.js ecmascript-6 es6-promise


【解决方案1】:

我的看法:

将 Promise 视为嵌套数组:

[then1,[then1.1, then1.2, catch1.1, then1.3, then1.4], then2, catch1]

看起来像这样:

new Promise(...)
.then(() => {         // <- 1
  return Promise(...)
          .then()     // <- 1.1
          .then()     // <- 1.2
          .catch()    // <- 1.1
          .then()     // <- 1.3
          .then()     // <- 1.4
})
.then()               // <- 2
.catch()              // <- 1

现在假设我们开始执行,它从最顶层的数组开始。我们对每个数组都有一个索引,指定我们在执行方面等待哪个元素。 我们首先调用.then1,它本身返回一个承诺链(另一个数组)。当发生错误时,您在层次结构数组中的最低层(最深的)跳过(不执行)它是元素,直到找到catch。如果是,它将执行catch 并继续执行其他元素。如果它没有找到catch,它会要求父数组找到并执行catch,跳过所有元素包括它的子数组,因为它们没有被捕获

在我们的示例中,如果then1.2 中发生错误,它将被catch1.1 捕获,但如果它发生在then1.3 中,它将一直传播到catch1,跳过then1.4then2

编辑:

下面是试验代码:

new Promise(res => res())
.then(() => 
    new Promise(res => res())
    .then(() => console.log(1))
    .then(() => {console.log(2); throw "Error 1"})
    .catch((err) => console.log(err))
    .then(() => {console.log(3); throw "Error 2"}))
    .then(() => console.log(4))
.then(() => 
    new Promise(res => res())
    .then(() => console.log(6))
    .then(() => {console.log(7); throw "Error 3"})
    .catch((err) => console.log(err))
    .then(() => console.log(8))
    .then(() => {console.log(9); throw "Error 4"}))
.then(() => console.log(10))
.catch((err) => console.log(err))

它记录:

1

2

错误 1

3

错误 2

【讨论】:

  • 是的,它就像一棵树,每个 Promise 都引用它的子节点。
  • 这并不完全正确。 Promise 不是嵌套数组,它们是嵌套的实体/上下文。我将在这里省略对分辨率上下文的引用,但它们更接近以下示例Promise.resolve(Promise.resolve(2)) 实际上是Promise(Promise(2))。 Promise.resolve(2).catch(console.log)`实际上是一个Catch(Promise(2))Promise.resolve(2).then(() =&gt; Promise.reject(new Error('2')).catch(console.log)Catch(Then(Promise))
  • 更短的流行语:它是组合,而不是嵌套
  • @RobertMennell 我从来没有说过它们是由嵌套数组实现的,只是做了一个类比,帮助同事更容易理解它:)
  • 除了在逐步完成这个例子之后,我看到了提问者想要澄清的同样的错误。正如您所说,第一个问题不在1.1 上。它实际上在1.2 上。如果我从1.1 分支出一个新的承诺并抛出,则该捕获将不会收到该链上的错误。
【解决方案2】:

所以让我们先来看看你的例子。

const p = new Promise(resolve => resolve(5))
 .then(v => 5*v)
 .then(v => { throw 'foo' });

p.then(v => v/4)
 .then(v => v+3)
 .catch(e => console.error('first catch:', e));

p.then(v => v/4)
 .then(v => v+3)
 .catch(e => console.error('second catch:', e));

这里实际上有 3 个不同的承诺:

pPromise.then.thenanonymousp.then.then.catchanonymousp.then.then.catch

请记住,Promise.thenPromise.catch 返回的 Promise 包含之前的 Promise。所以你最终得到的是带有这三个承诺链的嵌套承诺。

p 最终在评估时抛出第三个 Promise 时,其他两个 Promise 现在正在检查返回类型、标志和其他内部信息,最终导致它们实现链中较早的 Promise 被拒绝,因此它必须也拒绝。您还可以使用以下方法进行模拟:

var p = Promise.reject(new Error('Hi from p!'))

p.catch(e => console.log('Hello from a different promise', p))
==
p.catch(e => console.log('Hello from a yet different promise', p))

你应该注意到评估的返回是假的,这意味着两个对象不相等。

现在,如果我们稍等片刻,然后将一个新的 catch 处理程序附加到 p,会发生什么?

setTimeout(() => p.catch(console.log), 500)

您应该注意到另一个控制台日志,这次只有“Hi from p!”

原因是 Promise 的计算顺序。它在创建时进行评估,如果它是根据被拒绝的承诺创建的,则评估为被拒绝并转到 catch 处理程序。

你认为你会从以下得到什么?

Promise
  .reject(new Error('1'))
  .catch(console.log)
  .then(() => Promise.reject(new Error('2')))
  .then(() => new Error('3'))
  .then(console.log)
  .catch(console.log)
  .catch(() => console.log('4'))

没错,您会看到打印的 Error('1'),然后是打印的 Error('2'),但不会看到打印的 Error('3') 或打印的 '4'。这是因为 .catch 承诺会做以下两件事之一:将承诺解析为已解析的值,或者在链被拒绝时将承诺解析为函数值。

相比之下,.then 只解决了一个承诺。如果它的决议被拒绝,它会与决议一起拒绝。

是的,这有点令人困惑,但是一旦您意识到 promise 将等待解决并检查和链接 promise,就可以更容易地找出您将拒绝的地方。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-11-07
    • 2015-07-03
    • 1970-01-01
    • 1970-01-01
    • 2014-08-12
    • 2019-09-25
    • 2020-04-11
    相关资源
    最近更新 更多