Promise.prototype.finally 有什么作用?它会改变链路结果吗?
简化版
Promise.prototype.finally 用于在 Promise fulfilled 或 rejected 后都执行一段清理逻辑,比如关闭 loading、释放状态、记录结束时间。
正常情况下,finally 不改变前面的成功值或失败原因;但如果 finally 自己抛错,或者返回 rejected Promise,它会用新的错误覆盖原来的链路结果。
详细版
fetchUser()
.then(user => render(user))
.catch(err => showError(err))
.finally(() => hideLoading());
透传成功值:
Promise.resolve(1)
.finally(() => 2)
.then(value => console.log(value)); // 1
透传失败原因:
Promise.reject(new Error("A"))
.finally(() => {})
.catch(err => console.log(err.message)); // A
覆盖原结果:
Promise.resolve(1)
.finally(() => Promise.reject(new Error("cleanup failed")))
.catch(err => console.log(err.message)); // cleanup failed
完整版教学
一、finally 的设计目标
很多异步流程无论成功还是失败,都需要做同一件事。
start loading
├─ success -> render
└─ failure -> show error
end loading
finally 就是给“收尾动作”留的位置。
finally适合清理副作用,不适合生成新的业务值。
二、finally 与 then/catch 的关系
不用 finally 也能写,但会重复。
request()
.then(value => {
hideLoading();
return value;
})
.catch(error => {
hideLoading();
throw error;
});
使用 finally:
request()
.finally(() => hideLoading());
代码意图更集中。
三、正常返回时会透传原结果
Promise.resolve("ok")
.finally(() => "ignored")
.then(value => console.log(value)); // ok
finally 回调没有参数。它不关心前面是成功值还是失败原因。
| 前序状态 | finally 正常完成 | 后续状态 |
|---|---|---|
| fulfilled(value) | 正常 | fulfilled(value) |
| rejected(reason) | 正常 | rejected(reason) |
四、finally 抛错会覆盖结果
Promise.resolve("ok")
.finally(() => {
throw new Error("bad cleanup");
})
.catch(err => console.log(err.message));
收尾逻辑失败也是失败,因此会替换原来的成功结果。
如果前面已经失败,finally 再失败,也会用新的错误覆盖旧错误。
五、finally 返回 Promise 时会等待
Promise.resolve(1)
.finally(() => delay(100))
.then(value => console.log(value)); // 约 100ms 后输出 1
如果返回的 Promise rejected,链路变成 rejected。
Promise.reject(new Error("A"))
.finally(() => Promise.reject(new Error("B")))
.catch(err => console.log(err.message)); // B
六、实际工程场景
常见场景:
- 关闭 loading
- 释放按钮禁用态
- 清理临时变量
- 记录耗时
- 断开一次性订阅
const start = performance.now();
api.save(data)
.finally(() => {
metrics.timing("save", performance.now() - start);
});
七、常见误区与追问
- 误区:finally 的返回值会成为下一个 then 的值。 正常返回会被忽略,原值会透传。
- 误区:finally 只能处理成功场景。 fulfilled 和 rejected 都会执行。
- 误区:finally 里不会影响链路状态。 抛错或返回 rejected Promise 会覆盖原结果。
- 误区:finally 可以拿到前面的 value 或 reason。 回调没有业务参数。
- 追问:为什么 loading 适合放 finally? 因为成功和失败都需要关闭 loading。
- 追问:finally 返回 pending Promise 会怎样? 后续链路会等待它完成。
八、加强记忆
口诀:
finally normal -> pass through
finally throws -> replace with error
finally waits -> wait returned promise
回答时先说用途是统一清理,再说默认透传,最后补上“finally 自己失败会覆盖结果”这个高价值追问点。