真实面经题目 · 原创解析
Promise 的状态流转和 then 链式调用机制是什么?
Promise 的核心不是“异步容器”这么简单,而是一套状态机、回调注册机制、返回值吸收规则和微任务调度规则的组合。面试回答要把 pending 到 fulfilled 或 rejected 的单向迁移、then 返回新 Promise、回调异步执行、值穿透、异常传播、thenable 展开和微任务边界串成完整链路。
真实面经题目 · 原创解析
Promise 的核心不是“异步容器”这么简单,而是一套状态机、回调注册机制、返回值吸收规则和微任务调度规则的组合。面试回答要把 pending 到 fulfilled 或 rejected 的单向迁移、then 返回新 Promise、回调异步执行、值穿透、异常传播、thenable 展开和微任务边界串成完整链路。
Promise 有三种状态:pending、fulfilled、rejected。初始是 pending,一旦通过 resolve 进入 fulfilled,或通过 reject 进入 rejected,状态就不可再变。then 的作用不是直接修改原 Promise,而是给当前 Promise 注册成功和失败回调,并立即返回一个新的 Promise。当前 Promise 状态确定后,then 回调会被放入微任务队列异步执行。回调返回普通值,新 Promise fulfilled;抛出异常,新 Promise rejected;返回 Promise 或 thenable,则新 Promise 会采用它最终的状态。链式调用之所以成立,是因为每个 then 都返回全新的 Promise,后一个 then 监听的是前一个 then 返回的新 Promise,而不是最初那个 Promise。没有提供对应回调时会发生值穿透或错误穿透,使 fulfilled 值或 rejected 原因继续传递到后续链路。
Promise 首先是一个一次性状态机。它只有 pending、fulfilled、rejected 三种状态,创建后默认处于 pending。pending 可以迁移到 fulfilled,也可以迁移到 rejected,但 fulfilled 和 rejected 都是终态,不能再回到 pending,也不能互相切换。这个不可逆约束保证异步结果只被结算一次,即使 executor 中多次调用 resolve、reject,真正生效的也只有第一次。
fulfilled 不只是一个状态,还会绑定一个成功值;rejected 也不只是失败状态,还会绑定一个拒因。后续 then、catch、finally 处理的是状态携带的值或错误原因。pending 阶段没有最终结果,只能收集回调;一旦状态落定,已注册回调会按注册顺序进入异步调度,之后再注册 then 也不会同步执行回调,而是同样进入微任务队列。
then 的职责可以拆成两部分:给当前 Promise 注册 onFulfilled、onRejected 两类反应函数,并返回一个新的 Promise。很多人误以为 then 返回的是原 Promise,实际上链式调用的关键正是每次 then 都创建新 Promise。当前回调的执行结果会被用来结算这个新 Promise,所以链上每一环都有自己的状态和值,后续 then 订阅的是上一环产生的新 Promise。
Promise 的 executor 会在创建 Promise 时同步执行,但 then 注册的回调不会在当前同步调用栈里立刻执行。即使 Promise 已经 fulfilled,then 中的 onFulfilled 也会被放进微任务队列,等当前同步代码执行完后再运行。链上每个 then 回调执行后,新 Promise 的结算又会触发下一批 reaction 进入微任务队列,从而形成按微任务推进的异步流水线。
then 回调返回什么,决定它返回的新 Promise 如何结算。返回普通值,包括 undefined,新 Promise 会以这个值 fulfilled;回调内部抛出异常,新 Promise 会以该异常 rejected;返回另一个 Promise,新 Promise 会等待它完成并采用它的最终状态和值。这个规则让链式调用既能传递普通计算结果,也能把新的异步过程串入同一条控制流。
Promise Resolution Procedure 处理的是“用某个值 x 去 resolve 一个新 Promise”时应该发生什么。如果 x 是普通值,就直接 fulfilled;如果 x 是同一个 Promise,需要拒绝以避免循环;如果 x 是 Promise 或 thenable,就读取它的 then,并按 thenable 协议尝试接管它的最终状态。这个过程保证不同实现或类 Promise 对象也能被 Promise 链正确展开。
then 的两个参数都不是必填。如果 fulfilled 链路上没有提供 onFulfilled,成功值会原样传给下一个 then;如果 rejected 链路上没有提供 onRejected,拒因会继续向后传播,直到遇到 catch 或带失败回调的 then。值穿透解释了为什么空 then 不会吞掉结果,也解释了 catch 可以放在链条后部统一处理错误。
then 回调内部的同步异常会被 Promise 捕获,并转成返回的新 Promise 的 rejected 状态。之后如果后续 then 没有失败处理函数,错误会继续穿透;如果某个 catch 或 onRejected 返回普通值,链路会从 rejected 恢复到 fulfilled。也就是说错误不是全局外抛,而是在 Promise 链的状态系统中流转。
因为每个 then 回调都可能把上游结果转换成新的值、抛出新的错误,或返回新的异步任务。只有返回新 Promise,才能把这一环的处理结果作为下游输入,并让每一环都有独立状态。
不完全是。resolve 表示启动解析过程,如果传入普通值会 fulfilled;如果传入 Promise 或 thenable,则要等待并采用它的最终状态。因此 resolve 之后未必立即 fulfilled,也可能最终 rejected。
这是 Promise 调度规则的一部分,用来保证 then 回调不会打断当前同步调用栈,使执行顺序稳定可预测。无论注册 then 时 Promise 是否已结算,reaction 都通过微任务执行。
catch 本质上是 then(null, onRejected)。如果 catch 回调处理了错误并返回普通值,catch 返回的新 Promise 会变成 fulfilled,后续 then 的成功回调就会执行。只有再次抛错或返回 rejected Promise,失败才会继续传播。
Promise 是符合规范的对象,thenable 只是拥有 then 方法的对象或函数。Promise 解析过程会尝试接管 thenable 的状态,但 thenable 本身未必具备完整 Promise 行为,因此规范要处理多次调用、读取 then 抛错等边界。