先记住这个答案
finally 的回调不接收任何参数,且其返回值(包括解析后的 Promise)会被忽略,不改变链上传递的值或原因;而 then 的回调接收值或原因并可返回新值或 Promise 来改变后续链。若 finally 回调抛出异常或返回 rejected Promise,则新 Promise 以该异常拒绝;否则保持原状态透传。
- finally 回调不接收任何参数
- finally 返回值不改变链上值或原因
- finally 抛出异常会拒绝新 Promise
透传与截断:finally 的底层行为
当调用 promise.finally(onFinally) 时,内部会创建两个类似 then 的处理函数:一个用于原 Promise 成功时,另一个用于失败时。两者都执行 Promise.resolve(onFinally()) 等待回调完成,但成功分支忽略返回结果并重新传递原始值,失败分支则用 throw reason 重新抛出原始原因。因此 finally 天然透传原状态。
与 then(onFinally, onFinally) 相比,后者会接收参数并使用返回值作为新值。例如 Promise.resolve(2).then(()=>77) 得到 77,而 finally(()=>77) 仍为 2。若 finally 回调内部抛出异常或返回 rejected Promise,则透传被中断,新 Promise 以该异常拒绝,即使原先已成功。
加载状态与缓存更新:何时用 finally 而非 then
假设一个请求函数 fetchData() 返回 Promise,需要无论成功失败都关闭 loading,但成功后要更新数据。代码:fetchData().then(data=>{this.data=data;}).finally(()=>{this.loading=false;})。若把 this.loading=false 放到 then 中,则成功和失败分支都要写,而且失败分支还需 catch 处理,重复且易漏。
关键点:不能把设置数据放在 finally 中,因为 finally 不接收 data 且设置 this.data 会得到 undefined。同时如果 finally 回调中有可能抛错(如 console.log 不可用),将覆盖原失败原因,因此 loading 清理应保持简单无风险操作。
错误覆盖与链的中断条件
finally 回调若抛出同步异常,或返回一个 rejected Promise,则新 Promise 会以该新理由拒绝,原链上的成功值或失败原因被覆盖,导致后续 then/catch 收到新错误。这常发生在清理操作本身需要异步且失败时,但开发者未必预期该错误会影响主流程。
若要避免覆盖,可在 finally 内部捕获错误并记录,但要保证最终状态仍透传。例如 finally(()=>{try{cleanup();}catch(e){console.error(e);}}),代价是必须保证回调不向外部抛出。需权衡:是保留原始结果还是让清理错误冒泡。
容易答错的地方
- 认为 finally 回调可以接收并处理值
- 由于 finally 语义是清理,MDN 明确回调不接收任何参数,因此不能像 then 那样获取 fulfillment value 或 rejection reason。试图访问参数会得到 undefined,易导致逻辑错误。
- 误以为 finally 返回值会改变链上结果
- finally 的 fulfilled 返回值会被忽略,即使返回一个新值也不会替换原值。只有异常或 rejected Promise 才会影响链。示例:
Promise.resolve(1).finally(()=>2)最终仍为 1。
面试官还会怎么问?
finally 与 then(()=>{},()=>{}) 在行为上完全等价吗?
不完全等价。then 的回调接收参数,且其返回值会被用作新结果;而 finally 不接收参数并忽略成功返回值,只透传原值。此外 finally 对 subclass 的支持方式略有不同,但核心差异是参数与返回值的处理。
如果 finally 回调中返回一个异步清理任务,会等到它完成吗?
会。finally 会等待回调返回的 Promise 落定后才继续后续链。若清理任务被拒绝,则新 Promise 也会以该拒绝理由拒绝,从而影响后续。因此需要确保清理不被拒绝或预先捕获。
在 Promise 链末尾使用 finally 与在开头或中间使用有何不同?
位置不影响透传特性,但影响后续链。若中间使用,后续 then 仍能收到透传的值或原因;若末尾使用,则新 Promise 的最终状态取决于 finally 是否抛出异常。通常用于收尾,但中间使用可确保某段清理不中断依赖该值的后续。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。