在一个 Promise 中,then 和 finally 有什么区别?

29
我看到了Bluebird的finally文档,但我仍然不太理解与then相比的区别。
明确一下:我确切地知道为什么thencatch之后被调用。我希望它在捕获后被调用。这是意图。我的问题是:如果我希望代码始终被执行,而不管Promise的状态如何,那么thenfinally之间有什么区别呢?
我建立了这个测试:
var Promise = require("bluebird");

function test1 () {
    console.log("RESOLVE + THEN + CATCH + THEN");
    return new Promise((resolve, reject) => resolve())
       .then(() => console.log("then"))
       .catch(err => console.log("error:", err.message))
       .then(() => console.log("end"));
}

function test2 () {
    console.log("REJECT + THEN + CATCH + THEN");
    return new Promise((resolve, reject) => reject(new Error("rejected")))
       .then(() => console.log("then"))
       .catch(err => console.log("error:", err.message))
       .then(() => console.log("end"));
}

function test3 () {
    console.log("RESOLVE + THEN + CATCH + FINALLY");
    return new Promise((resolve, reject) => resolve())
       .then(() => console.log("then"))
       .catch(err => console.log("error:", err.message))
       .finally(() => console.log("end"));
}

function test4 () {
    console.log("REJECT + THEN + CATCH + FINALLY");
    return new Promise((resolve, reject) => reject(new Error("rejected")))
       .then(() => console.log("then"))
       .catch(err => console.log("error:", err.message))
       .finally(() => console.log("end"));
}

// run tests "sequentially" so console output doesn't get blended
setTimeout(test1, 500);
setTimeout(test2, 1000);
setTimeout(test3, 1500);
setTimeout(test4, 2000);

我将为您翻译关于编程的内容,以下是需要翻译的文本:

This tests four cases:

  1. .then(...).catch(...).then(...) with a resolved promise.
  2. .then(...).catch(...).then(...) with a rejected promise.
  3. .then(...).catch(...).finally(...) with a resolved promise.
  4. .then(...).catch(...).finally(...) with a rejected promise.

我的测试结果显示,情况1和2与情况3和4的行为完全相同:无论之前发生了什么,最后一个部分(取决于测试的thenfinally)都会执行,这是预期的。该程序的输出如下:

RESOLVE + THEN + CATCH + THEN
then
end
REJECT + THEN + CATCH + THEN
error: rejected
end
RESOLVE + THEN + CATCH + FINALLY
then
end
REJECT + THEN + CATCH + FINALLY
error: rejected
end

我问这个问题的原因是因为我看到了在我提出的另一个问题上的评论

不确定你的promise是否支持它,但是你应该将最后的.then更改为.finally,以便busy始终被清除。

根据我极其有限的关于then的知识和以上的测试,似乎then是足够的。但是在看到那个评论之后,我开始质疑自己和使用then执行“finally”代码的安全性。

所以我的问题是: thenfinally之间有什么区别? 他们看起来好像行为相同,但是我何时需要使用finally而不是then


1
如果你总是捕获被拒绝的 Promise,也就是说你只有已解决的 Promise,那么当然没有什么区别了...我是否误解了你的问题?这一切似乎都很明显。 - Kevin B
1
这是针对承诺可能被解决或拒绝的情况。在您的情况下,它总是被解决,因此没有区别。 - Kevin B
2
.catch 使其成为已解决的 Promise。 - Kevin B
2
@JasonC,第一:有时候你不想在错误发生的地方捕获它们,而是在使用该函数的代码中捕获它们;所以你不会捕获它们。在这种情况下,你不能替换 then()finally()。有时候你必须清理某些东西,无论是否出现错误。(清空引用、清除超时等等)这就是你使用 finally() 的地方。第二:你传递给 catch() 的函数也可能抛出异常,那么你将得到一个被拒绝的 Promise,接下来的 then() 将不会被调用。 - Thomas
1
好了,现在你无法删除它。问题解决了! - Kevin B
显示剩余11条评论
3个回答

36
第一点区别:有时你不想在错误产生的地方捕获错误,而是在使用此函数的代码中捕获它们,这样你就无法替换then()finally()
有时候你必须清理一些东西,无论是否出现错误(将引用置为null,清除超时等等),这就是你使用finally()的地方。
第二点区别:你传递给catch()的函数也可能会抛出异常,那么你将得到一个被拒绝的Promise,以下的then()将不会被调用。
比如说,在catch()之前加一个finally(),这样即使出现错误,finally()仍然会执行,这是finally()的目的。它将在任何情况下执行,而不改变已解决的值。
你可能想阅读/谷歌一下关于try {} finally {},没有catch

9

.then.finally 不同。

.then 是主要的 Promise 原语。它在Promises/A+规范中得到了全面定义,所有 Promise 库都会实现它。

Bluebird .finally 处理程序将 "无论 Promise 的命运如何都会被调用"。所以未处理的异常仍会触发 .finally

new Promise((resolve, reject) => reject(false))
  .finally(a => console.log('finally', a))
// finally undefined
// Unhandled rejection false

new Promise((resolve, reject) => reject(false))
  .then(a => console.log('then', a))
// Unhandled rejection false

.finally不会改变promise的解析值,也不会接收promise链的结果。

new Promise((resolve, reject) => reject(false))
  .catch(e => {
    console.log(e)
    return 2
  })
  .finally(a => {
    console.log('finally', a)
    return 1
  })
  .then(res => console.log('res', res))
// finally undefined
// res 2

在您的测试用例中,这些方法看起来很相似,因为测试可以捕获所有错误,并且您只使用承诺来进行流程控制,而不依赖于承诺链中的值被解决/拒绝。

3
顺带一提,如果有其他人在阅读这篇文章,现在我已经理解了这些概念,并发现了一些其他的东西,其中之一是 .tap(): tap 有点类似于 thenfinally 的中间状态:像 then 一样,在被拒绝时不会被调用,但像 finally 一样,它不会改变结果,因此前一个结果会 "跳过" 它。很棒。 - Jason C

6
好的,经过一些讨论和来自KevinB的大量帮助,我至少找出了一个区别。请考虑以下两个新测试:
function test5 () {
    console.log("REJECT + THEN + CATCH/THROW + THEN");
    return new Promise((resolve, reject) => reject(new Error("rejected")))
       .then(() => console.log("then"))
       .catch(function(err) { throw new Error("error in catch"); })
       .then(() => console.log("end"));
}

function test6 () {
    console.log("REJECT + THEN + CATCH/THROW + FINALLY");
    return new Promise((resolve, reject) => reject(new Error("rejected")))
       .then(() => console.log("then"))
       .catch(function(err) { throw new Error("error in catch"); })
       .finally(() => console.log("end"));
}

在这些情况下,承诺被拒绝,但是从catch中抛出了一个错误。
在两种情况下,承诺最终都被拒绝,但对于finally情况,finally仍然会执行,而then则不会执行。
所以这就是区别。它们几乎相同,唯一的例外是当从catch处理程序中抛出错误时,finally会执行,而then不会执行。
这意味着我引用的评论也有价值:如果在我的错误处理程序中发生另一个错误,则then不能保证清理,但finally可以。这就是我错过的情况。

2
此外,.finally 不会影响 Promise 解析的值,而 .then 会。 - Matt
@Matt,如果函数返回undefined,那么就不会了。但是你说得对,finally()保证了已解决的值不会被更改。 - Thomas
@Thomas 抱歉,我不明白你所说的“undefined”警告是什么意思?你是指如果到处返回“undefined”,你就看不到效果了吗? - Matt
据我所知,JS 中的每个主要 Promise 实现,如果您传递给 then() 的函数没有返回值(即 undefined),则不会更改解析的值,而是解析为先前的值。@Matt - Thomas
1
@Thomas Native和Bluebird将解决“undefined”,因此我认为这是规范。new Promise(resolve => resolve(true)).then(res => console.log('res',res)).then(res => console.log('res', res)) - Matt

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