前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
首页程序员面试题库Promise finally 与 then 区别
JavaScript异步与 Promise

Promise 的 finally 和 then 回调有什么不一样?

finally 回调不管成功失败都会执行,但它拿不到结果,参数是空的;它的返回值也会被忽略,上一步的成功值或失败原因会原样传给后面。then 回调则能拿到值,返回什么,下一步就收到什么。

前端进阶之旅 · 一题精讲更新于 2026.09.29
JavaScript#异步与 Promise#异步编程
先看核心答案读代码示例
理解线索

`finally` 是旁观者,`then` 是加工者

  1. 没有参数finally 不知道上一步成功还是失败,只适合做和结果无关的收尾
  2. 结果穿透回调返回的普通值被丢弃,下一步收到的还是原来的值或原因
  3. 异常覆盖回调里 throw 或返回 rejected Promise,会替换掉原来的结果

finally 回调如果返回一个还没完成的 Promise,链会等它完成再往下走。所以在 finally 里做异步清理时,要把 Promise return 出去,否则不会被等待。

核心回答

先记住这个答案

finally 和 then 最大的区别有三点。第一,finally 回调没有参数,它不知道上一步是成功还是失败。第二,finally 的返回值会被忽略,上一步的成功值或失败原因原样传给下一步,而 then 返回什么,下一步就拿到什么。第三,只有在 finally 里抛错,或者返回一个 rejected 的 Promise,才会把结果改掉。所以 finally 只适合做关 loading、释放资源这种和结果无关的收尾工作。

  • finally 回调没有参数,拿不到值也拿不到错误
  • finally 的返回值被忽略,原结果穿透下去
  • finally 里抛错会把结果改成这个新错误
  • finally 不会吞错误,失败照样要 catch

用一段代码看清三个区别

下面把三种情况放在一起跑,每一步都 await,顺序就是打印顺序。

参数、返回值、抛错JavaScript
async function main() {
  // 1. finally 回调没有参数
  await Promise.resolve('data').finally((...args) => console.log('参数个数:', args.length))

  // 2. 返回值被忽略,原来的值穿透下去
  const v = await Promise.resolve('原始值').finally(() => '新值')
  console.log('拿到:', v)

  // 3. 失败原因也会穿透,finally 不会吞错误
  await Promise.reject(new Error('请求失败'))
    .finally(() => 'ignored')
    .catch((err) => console.log('catch 到:', err.message))

  // 4. finally 里抛错会覆盖原结果
  await Promise.resolve('原始值')
    .finally(() => { throw new Error('清理失败') })
    .catch((err) => console.log('catch 到:', err.message))
}
main()
查看输出与解释
参数个数: 0
拿到: 原始值
catch 到: 请求失败
catch 到: 清理失败

第 2 步如果把 finally 换成 then(() => '新值'),v 就会变成 新值。这就是「加工者」和「旁观者」的区别:then 回调的返回值会成为新 Promise 的结果,finally 回调的返回值直接被丢弃。 第 3 步说明 finally 不是 catch。错误经过 finally 后依然是错误,后面没有 catch 的话,照样会报未处理的拒绝。

为什么设计成拿不到结果

finally 对应的是同步代码里 try...catch...finally 的 finally 块。同步的 finally 块本来就不接收返回值或异常,它的职责是「不管怎样都要做」,比如关掉文件句柄。Promise 的 finally 照搬了这个语义。

在它出现之前,要实现同样效果只能写 p.then(cleanup, cleanup)。但这样写有两个问题:cleanup 会收到值或错误作为参数;更麻烦的是,cleanup 的返回值会替换掉原来的结果,失败还会被转成成功。finally 解决的正是这两点,它等价于下面这种写法:成功时执行回调再把原值返回,失败时执行回调再把原错误重新抛出。

什么时候 finally 会改变结果
只有两种情况。一是回调里同步 throw;二是回调返回一个最终 rejected 的 Promise。这两种情况下,原来的值或错误都会被新错误替换。返回一个 fulfilled 的 Promise 不会改结果,但链会等这个 Promise 完成后才继续,这可以用来在 finally 里做异步清理,比如等上报日志发完再往下走。

实际项目里最常见的用法

最典型的场景是关 loading。请求成功要关,失败也要关,以前要在 then 和 catch 里各写一遍,现在写一次就够。

用 finally 关 loading
比如提交表单时,先把 loading 设成 true,然后 submitForm(data).then(showSuccess).catch(showError).finally(() => { loading = false })。用 async/await 写就是 try { await submitForm(data) } catch (e) { showError(e) } finally { loading = false },两种写法效果一样。 这里有个顺序问题:finally 放在 catch 后面,它执行时错误已经被处理了,链的最终结果是成功;放在 catch 前面,错误会穿过 finally 再被 catch 接住。关 loading 这种不关心结果的操作放哪都行,但如果 finally 后面还接了 then,位置就会影响后面拿到的是什么。
回答前,多想一步

容易答错的地方

以为 finally 能拿到结果
很多人写 finally((res) => console.log(res)),结果打印出 undefined。finally 回调不接收任何参数,需要结果就在前面的 then 里处理。
以为 finally 能处理错误
finally 执行完,错误会继续往后传。只写 finally 不写 catch,控制台照样会报 Uncaught (in promise)。
以为 finally 里 return 能改结果
return 普通值、return 一个 fulfilled 的 Promise 都不会改结果。能改结果的只有抛错和返回 rejected Promise,而且改成的一定是失败,没法把失败改成成功。
试着用自己的话回答

面试官还会怎么问?

手写一个 Promise.prototype.finally 怎么写?

核心是 this.then(onSuccess, onFail)。成功时 return Promise.resolve(callback()).then(() => value),失败时 return Promise.resolve(callback()).then(() => { throw reason })。用 Promise.resolve 包一层,是为了在回调返回 Promise 时等它完成;后面的 then 把原值或原错误重新交出去。

finally 回调是同步执行还是异步执行?

和 then 一样是异步的,Promise 状态确定后作为微任务执行。而且因为内部多包了一层 then,finally 后面接的回调会比直接接 then 晚几个微任务,打印顺序题里偶尔会考到。

async 函数里 try...finally 的 finally 块,和 Promise 的 finally 一样吗?

语义基本一致,都是无论成功失败都执行。一个区别是同步风格的 finally 块里写 return 会覆盖 try 里的返回值,而 Promise 的 finally 回调里 return 普通值会被忽略。

从一道题,走向一组知识

把知识连起来

异步与 Promise

then 回调里 throw 一个错误,后面的链会怎样?

finally 里抛错会改写结果,靠的正是链上任意回调 throw 都会变成拒绝这条规则。

异步与 Promise

catch 放在 Promise 链中间和放在末尾,结果有什么不一样?

finally 不处理错误,把 catch 放在 finally 前面还是后面,决定了清理代码能不能看到恢复后的结果。

放回知识体系中复习JavaScript 进阶知识

参考资料

  • Promise.prototype.finally()

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 用一段代码看清三个区别
  3. 为什么设计成拿不到结果
  4. 实际项目里最常见的用法
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑