JavaScript Promise 链接

3

我正在学习来自MDN文档的Promise。第一个示例演示了thencatch方法:

// We define what to do when the promise is resolved/fulfilled with the then() call,
// and the catch() method defines what to do if the promise is rejected.
p1.then(
    // Log the fulfillment value
    function(val) {
        log.insertAdjacentHTML('beforeend', val +
            ') Promise fulfilled (<small>Async code terminated</small>)<br/>');
    })
.catch(
    // Log the rejection reason
    function(reason) {
        console.log('Handle rejected promise ('+reason+') here.');
    });

文档说明then方法返回一个新的Promise,所以上述代码应该等同于:
var p2 = p1.then(
    // Log the fulfillment value
    function(val) {
        log.insertAdjacentHTML('beforeend', val +
            ') Promise fulfilled (<small>Async code terminated</small>)<br/>');
    });
p2.catch(
    // Log the rejection reason
    function(reason) {
        console.log('Handle rejected promise ('+reason+') here.');
    });

那么,这是否意味着只有当从 p1.then 返回的 promise 被解析为拒绝时,才会调用catch 回调函数,而不是 promise p1?我需要这样做:

p1.then( /* etc. */ );
// and for rejected resolutions
p1.catch( /* etc. */ );

如何在捕获 p1 的拒绝(rejection)时,不将 catch 方法链接到 then 方法中?

起初,我以为从 p1.then 返回的 Promise 与 p1 相同,就像 jQuery 大部分 API 的做法一样。但下面的示例清楚地表明这两个 Promise 是不同的。

var p1 = new Promise(function(resolve, reject) { 
  resolve("Success!");
});

console.log(p1);
// Promise { <state>: "fulfilled", <value>: "Success!" }

var p2 = p1.then(function(value) {
  console.log(value);
});
// Success!

console.log(p2); 
// Promise { <state>: "fulfilled", <value>: undefined }

此外,我在JSFiddle中尝试了这三种方法:

  1. p1.then(onFulfilled).catch(onRejected);
  2. p1.then(onFulfilled); p1.catch(onRejected);
  3. p1.then(onFulfilled, onRejected);

这三种方法都可行。我可以理解后两种方法,但我的问题是:为什么第一种方法也能运行成功呢?


可能的错误会通过 Promise 链一直传递,直到找到一个合适的处理程序。因此,即使 p2 Promise 与 p1 不同,p1 中的任何错误(拒绝)都会传播到 p2,除非 p1 有一个单独的 catch() - Sirko
.catch(onRejected)看作是.then(null, onRejected)(因为在早期的Promise实现中,它基本上就是这样 - 当你理解后两者时,你也应该理解第一个)。 - Jaromanda X
@JaromandaX:它仍然是,不确定您所说的“早期承诺实现”是什么意思。 - Bergi
@bergi... 在我读的 Promises 的第一个规范中甚至没有提到 .catch,以至于我早期使用 Promises 时经常使用 .then(null, onError) 这种类型的代码(尽管我的记忆可能有些混乱)。 - Jaromanda X
@JaromandaX:当然可以,但是从引入该代码的那一刻起(可能是这里),它就是then(null, onRejected)的别名。 - Bergi
3个回答

7
首先,让我们了解一下 promise 相关部分的背景:

p1.then(...) 返回一个与之前链接的新 promise。因此,p1.then(...).then(...) 只有在第一个 .then() 处理程序完成后才会执行第二个处理程序。如果第一个 .then() 处理程序返回一个未实现的 promise,则它将等待该返回 promise 解决后再解决这个第二个 promise 并调用那个第二个 .then() 处理程序。

其次,在 promise 链中任何地方拒绝时,它会立即跳到链底(跳过任何已满足的处理程序),直到到达第一个拒绝处理程序(无论是来自 .catch() 还是来自 .then() 的第二个参数)。这是 promise 拒绝的非常重要的部分,因为它意味着您不必在 promise 链的每个级别上捕获拒绝。您可以在链的末尾放置一个 .catch(),并且在链中发生的任何拒绝都将直接转到该 .catch()
另外,值得注意的是,.catch(fn) 只是 .then(null, fn) 的快捷方式。它的工作方式没有任何区别。
此外,请记住,(就像 .then() 一样).catch() 也会返回一个新的 promise。如果您的 .catch() 处理程序本身没有抛出或返回拒绝的 promise,则拒绝将被视为“已处理”,并且返回的 promise 将解决,从而允许链从那里继续。这使您可以处理错误,然后有意决定是否要使用正常实现逻辑继续链或保持拒绝状态。
现在,针对您的具体问题...

如果是这样,那么 catch 回调函数是否只会在从 p1.then 返回的 promise 而不是 p1 本身解析为拒绝时才被调用?我是否需要执行以下操作:

否。拒绝立即向下传播到链中的下一个拒绝处理程序,跳过所有解决处理程序。因此,在您的示例中,它将跳到下一个 .catch()
这是使 promise 错误处理变得更加简单的事情之一。您可以在链的末尾放置一个 .catch(),它将捕获来自链中任何位置的错误。
有时在链的中间截取错误是有原因的(如果您想在错误上分支并更改逻辑,然后继续使用其他代码),或者如果您想“处理”错误并继续。但是,如果您的链是全有或全无的,则可以将一个 .catch() 放在链的末尾以捕获所有错误。
它的作用类似于同步代码中的try / catch块。在链的末尾放置一个.catch()就像在一堆同步代码周围的最高级别放置一个try / catch块一样。它将捕获代码中的任何异常。 所有三种方法都有效,我可以理解后两种。我的问题的要点是为什么第一种方法也有效? 这三种方法几乎相同。 2和3是相同的。事实上,.catch(fn)只是.then(null, fn)的快捷方式。
选项1略有不同,因为如果onFulfilled处理程序抛出或返回拒绝的承诺,则.catch()处理程序将被调用。在其他两个选项中,情况并非如此。除了这一个区别之外,它将按照相同的方式工作(如您所观察到的)。
选项1之所以有效,是因为拒绝会向下传播。因此,如果p1拒绝,或者如果onFulfilled处理程序返回拒绝的承诺或抛出异常,则.catch()处理程序将被调用。

3

代码不应该是等价的吗?

确实如此。

那么,这是否意味着仅当从p1.then返回的Promise而不是p1本身被解析为拒绝时,才会调用catch回调函数?

是的,完全正确。

然而,当p1被拒绝时,p2也将被拒绝,因为您没有传递.then()调用的onRejected处理程序来截取它。拒绝只是向下传播。

我在JSFiddle上尝试了三种方法。所有三种方法都有效。

的确如此,但它们并不相同。

p1.then(onFulfilled, onRejected);

这通常是您所需做的。

p1.then(onFulfilled); p1.catch(onRejected);

这最终会产生两个不同的 Promise,其中一个将被解决并返回onFulfilled结果或拒绝,另一个将被实现或解决并返回onRejected结果。

p1.then(onFulfilled).catch(onRejected);

这是一个与第一个不同的问题,可以参考当使用.then(success, fail)被视为Promise反模式?


1

这个:

var p2 = p1.then()
p2.catch()

is the same as this:

p1.then().catch()

你也可以这样做:

p1
 .then(response => response.body)
 .then(body => JSON.parse(body))
 .then(data => console.log(data))
 .catch(e => console.log('something somewhere failed'))

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