Promise.prototype.finally 适合隐藏骨架屏,因为它在 Promise settled 后必执行,不干扰返回值且不吞错误;需置于链末端或 try/finally 块中,避免竞态与错误覆盖。

Promise.prototype.finally 为什么适合隐藏骨架屏
因为 finally 不关心上一个 Promise 是 fulfilled 还是 rejected,只要状态 settled 就执行,天然契合“请求结束就收起骨架屏”的语义。它不接收参数,不会干扰链式返回值,也不会意外吞掉错误——这点比在 then 和 catch 里重复写隐藏逻辑更安全。
在 fetch 请求中正确使用 finally 隐藏骨架屏
常见错误是把 finally 放错位置,比如写在 catch 后面却忘了 then 也可能抛错(例如 response.json() 解析失败),导致骨架屏卡住。正确做法是让 finally 紧跟在最终的 Promise 链末端:
showSkeleton();
fetch('/api/data')
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
})
.then(data => render(data))
.catch(err => handleError(err))
.finally(() => hideSkeleton());
注意:如果中间用了 async/await,finally 不能直接链在 await 表达式后,得包一层 Promise:
showSkeleton();
try {
const res = await fetch('/api/data');
const data = await res.json();
render(data);
} catch (err) {
handleError(err);
} finally {
hideSkeleton();
}
finally 中修改状态可能引发竞态问题
当多个并发请求共用同一个骨架屏开关(比如全局 isLoading 变量),直接在每个请求的 finally 里设 isLoading = false 会导致后完成的请求覆盖先完成的,骨架屏提前消失。解决方法:
- 用计数器:发起请求前
pendingCount++,finally里pendingCount--,仅当pendingCount === 0时隐藏 - 用 AbortController 配合状态标记,或改用 React Query / SWR 等自带 loading 状态管理的库
- 避免共享状态,每个组件维护自己的
isPending,靠 UI 层统一控制显示逻辑
finally 不会阻止错误向上传播,但容易误以为它会
这是最常被忽略的一点:finally 里的代码执行完后,原始 rejection 仍会继续冒泡。如果你在 finally 里写了 throw 或返回 rejected Promise,它会覆盖原错误;但什么也不做,就不会影响错误流向。所以以下写法是安全的:
fetch('/api/data')
.then(handleSuccess)
.catch(handleError)
.finally(() => hideSkeleton()); // ✅ 原错误仍能被外层 try/catch 捕获
而这样会出问题:
.finally(() => {
hideSkeleton();
return Promise.reject(new Error('finally broke it')); // ❌ 覆盖了原始错误
})
实际项目中,只要 finally 体里只做副作用(如 DOM 更新、日志、清理定时器),就不需要担心错误流被破坏。










