Promise 的核心价值在于用链式调用替代层层嵌套的回调函数,而 then、catch、finally 正是构建这条链的三块基石。理解它们的用法和返回值规则,是写出清晰异步代码的前提。
then — 处理“成功”与“失败”的统一入口
then 是 Promise 实例上最核心的方法,它接受两个可选的回调函数作为参数:
promise.then(onFulfilled, onRejected);
onFulfilled:当 Promise 状态变为 fulfilled(成功)时被调用,参数是 Promise 的结果值。onRejected:当 Promise 状态变为 rejected(失败)时被调用,参数是 Promise 的拒绝原因。
两个参数都是可选的,如果传入的不是函数,会被忽略,并发生“值穿透”(稍后详述)。
关键规则:then 始终返回一个新的 Promise
这是链式调用的根基。无论 onFulfilled 还是 onRejected 内部返回什么值,then 都会将其包装成一个新的 Promise,传递给链上的下一个 then。
fetch('/api/user')
.then(response => response.json()) // 返回新 Promise
.then(user => {
console.log(user.name); // 处理上一个 then 的结果
return user.id; // 返回值会传递给下一个 then
})
.then(id => fetch(`/api/orders/${id}`))
.then(orders => console.log(orders));
如果在 then 的回调中手动返回一个 Promise,那么后续的 then 会等待这个 Promise 完成,并拿它的结果继续。如果回调中抛出异常,then 会返回一个 rejected 的 Promise,直接跳转到后续的 catch。
catch — 错误集中捕获
catch 实质上是 then(undefined, onRejected) 的语法糖,专门用来处理链中任何一个环节抛出的错误。推荐把 catch 放在链的末尾,这样无需在每个 then 中写错误处理逻辑。
fetch('/api/data')
.then(res => res.json())
.then(data => processData(data))
.catch(error => {
console.error('请求或处理过程中出错:', error);
// 可以做统一错误提示、上报等
});
错误会穿透:如果链中间某个 then 没有提供 onRejected 回调,错误会自动沿着链向下传递,直到遇到第一个 catch。这意味着你可以把错误处理集中在一处,而不用在每个环节都写一遍。
需要注意的是,catch 也会返回一个新的 Promise。如果 catch 内部正常返回一个值,链可以继续正常执行。利用这一点可以实现错误恢复:
fetch('/api/backup')
.then(res => res.json())
.catch(err => {
console.warn('主请求失败,使用默认数据');
return { name: '默认用户' }; // 返回了一个兜底值
})
.then(data => console.log(data)); // 始终能拿到 data
finally — 无论如何都会执行
finally 中的回调会在 Promise 敲定(无论是 fulfilled 还是 rejected) 之后执行,它不接受任何参数,也不影响最终结果的传递。典型的用途是清理工作:隐藏加载动画、关闭数据库连接、停止计时器等。
let loading = true;
fetch('/api/data')
.then(res => res.json())
.then(data => render(data))
.catch(err => showError(err))
.finally(() => {
loading = false; // 无论成功还是失败,都要关闭加载状态
});
finally 也会返回一个新的 Promise,除非 finally 回调内部抛出错误,否则它会“原样传递”前一个 Promise 的结果或拒绝。这意味着写在 finally 后面的 then 或 catch 仍然能拿到原始的结果。
Promise.resolve('成功')
.finally(() => '这个值会被忽略') // 不改变结果
.then(console.log); // 输出:成功
Promise.reject('失败')
.finally(() => '忽略')
.catch(console.log); // 输出:失败
值穿透与链式调用总结
当 then / catch 接收到的参数不是函数时,会发生“值穿透”,即前一个 Promise 的状态和值会直接传递到下一个有效处理函数:
Promise.resolve(42)
.then(1) // 不是函数,忽略
.then(null) // 不是函数,忽略
.then(value => console.log(value)); // 42
理解以上规则后,任何复杂的异步流程都可以用一条清晰的 Promise 链来表达,而且错误处理和清理逻辑天然内聚,再也不需要嵌套的回调地狱。