Promise 的状态和链式调用原理是什么?
简化版
Promise 有 pending、fulfilled、rejected 三种状态,状态一旦改变就不可逆。then 会返回新的 Promise,因此可以链式调用;回调返回普通值会包装成成功 Promise,抛错或返回 rejected Promise 会进入后续失败链路。
详细版
Promise 状态:
pending:等待中。fulfilled:已成功。rejected:已失败。
链式调用示例:
fetchUser()
.then(user => fetchOrders(user.id))
.then(orders => render(orders))
.catch(err => showError(err));
then 的返回值决定下一个 then 收到什么:
- 返回普通值:下一个
then接收该值。 - 返回 Promise:下一个
then等它完成。 - 抛出错误:进入最近的
catch。
Promise 回调属于微任务,所以会在同步代码之后、下一个宏任务之前执行。
完整版教学
一、Promise 解决了什么问题
Promise 主要解决异步流程组织问题。回调嵌套太深会形成回调地狱,错误处理分散,流程难读。Promise 把异步结果抽象成一个“未来会完成或失败的值”,让结果和值的错误沿同一条链组合。它本身并不会自动取消底层网络或定时器,取消仍需 AbortSignal 等业务协议配合。
二、状态只能改变一次
Promise 从 pending 变成 fulfilled 或 rejected 后,就不能再改变。
const p = new Promise((resolve, reject) => {
resolve(1);
reject(new Error('fail'));
});
这里最终只会成功为 1,后面的 reject 无效。这保证了异步结果稳定。
三、then 为什么能链式调用
then 不会返回原来的 Promise,而是返回一个新的 Promise。新 Promise 的状态由回调执行结果决定。
Promise.resolve(1)
.then(x => x + 1)
.then(x => console.log(x)); // 2
第一个回调返回 2,这个值成为第二个 then 的输入。
四、错误传播机制
Promise 链中的错误会向后传播,直到遇到 catch 或第二个失败回调。
Promise.resolve()
.then(() => {
throw new Error('boom');
})
.catch(err => console.log(err.message));
这让异步错误处理更接近同步代码的 try/catch 思路。
五、面试追问与工程落地
Promise 高频追问是“then 里返回 Promise 会怎样”。如果返回的是 Promise,外层新 Promise 会采用这个 Promise 的最终状态;如果返回的是 thenable 对象,也会尝试按 thenable 解析。这就是 Promise 链能扁平化异步流程的原因。
还会追问 catch 后链路是否继续。catch 本质上也是返回新 Promise。如果 catch 里没有继续抛错,而是返回普通值,后面的 then 会走成功分支。这在错误兜底、降级数据、重试逻辑中很常见。
工程中要小心“吞错”。如果 catch 只打印日志但不抛出,调用方会以为流程成功。公共请求库通常要统一错误规范:哪些错误在内部转成业务提示,哪些错误继续 reject 给页面处理。
六、区分 resolved、settled 与 thenable 吸收
“resolved 就等于 fulfilled”是不准确的。一个 Promise 可以通过 resolve(otherPromise) 锁定到另一个仍 pending 的 Promise,此时它已经 resolved,却还没有 settled;只有采用对象最终 fulfilled 或 rejected 后,外层才进入对应终态。规范中的解析过程还会读取普通对象的 then,把 thenable 的结果吸收到新 Promise 中。
let release;
const inner = new Promise(resolve => { release = resolve; });
const outer = new Promise(resolve => resolve(inner));
// outer 已锁定跟随 inner,但仍 pending
release(42);
outer.then(console.log); // 42
| 回调结果 | then 返回的新 Promise 如何处理 |
|---|---|
返回 7 | fulfilled 为 7 |
| 抛出 Error | rejected 为该 Error |
| 返回 Promise | 采用该 Promise 最终状态 |
返回带可调用 then 的对象 | 异步执行 thenable 解析 Job |
| 返回结果 Promise 自己,形成自解析 | rejected 为 TypeError |
同一个 Promise 连续发起 3次处理:resolve(1)、reject(2)、resolve(3),只有第一次解析尝试能决定后续,结果为 fulfilled 的 1。但如果第一次传入 thenable,外层可能暂时保持 pending,这正是“状态只能变一次”与“解析不一定立刻完成”可以同时成立的原因。
then每次都创建新的 Promise;画链路时不要把“原 Promise 的状态”和“回调返回值决定的新 Promise 状态”混在一起。
七、常见误区与追问
- 误区:Promise 构造器会自动把 executor 放到微任务执行。 executor 在构造时同步调用,Promise reaction 才通过宿主调度的 Job 异步执行。
- 误区:Promise 一旦 resolved 就一定已经 fulfilled。 resolved 也可能表示锁定跟随另一个尚未 settled 的 Promise 或 thenable。
- 误区:
then(fn)会修改并返回原 Promise。 它创建并返回新的 Promise,所以同一源 Promise 可以派生多条互不相同的链。 - 追问:为什么 thenable 的
then要特殊处理? Promise 解析协议需要兼容其他 Promise 实现,通过读取并调用可调用的then来采用其最终结果。 - 追问:
catch后为什么可能进入成功分支? catch 返回普通值等于恢复为 fulfilled;若要让错误继续传播,必须重新抛出或返回 rejected Promise。 - 追问:
finally能否替换链路的值或错误? 正常完成的 finally 通常透传原结果,但如果它抛错或返回 rejected Promise,新错误会覆盖原结局。 - 追问:未处理 rejection 何时被报告? 宿主在微任务检查点等时机跟踪是否已有 rejection handler,具体报告事件由浏览器或 Node 的宿主规则决定。
八、加强记忆
Promise 可以记成“一个只能兑现一次的承诺”。状态不可逆,then 返回新 Promise,返回值决定下一步,抛错会沿链路找 catch。