跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

如何手动实现一个具备“全状态追溯”与“链式调用”的标准 Promise 引擎

真正符合Promises/A+规范的Promise必须严格遵循四条铁律:三态单向流转(pending→fulfilled/rejected不可逆)、微任务调度(queueMicrotask优先)、then返回新实例、错误穿透(try/catch+reject传递)。 手动实现一个真正符合 Promises/A+ 规范、能支持全状态追溯和链式调用的 Promise,核心不在“能不能写出来”,而在于是否严格遵循三态单向流转、微任务调度、返回新实例、错误穿透这四条铁律。任何一处松动(比如用
setTimeout
替代
queueMicrotask
,或在
then
里直接返回
this
),都会导致链式中断、状态错乱或测试套件失败。 状态机必须封闭且不可逆,不能靠 if-else 模拟 Promise 的
PENDING
FULFILLED
REJECTED
是单向的,不是“当前状态是 pending 就执行 resolve”。很多手写版本只做
if (this.status === 'pending') { this.status = 'fulfilled' }
,看似合理,但漏掉了关键约束:一旦状态变更,后续所有
resolve
/
reject
调用必须被忽略。 实操建议: 用闭包变量(如
state
value
reason
)保存内部状态,初始为
'pending'
resolve
reject
函数开头必须加
if (state !== 'pending') return
,否则多次调用会污染状态 不要用
this.$$status
这类可被外部篡改的属性,状态必须私有化 executor 必须同步执行,但其内部调用
resolve
/
reject
可以是异步的——这点常被误读为“executor 可以异步”,实际是禁止的
then
必须返回新 Promise,且返回值处理逻辑不能简化
then
返回的不是原实例,也不是任意对象,而是一个全新
MyPromise
实例,它的终态完全由上一个回调的执行结果决定。所谓“全状态追溯”,指的就是这个新 Promise 能准确承接前序的
fulfilled
值、
rejected
原因,甚至嵌套的 thenable 对象。 实操建议: 每个
then
调用都应创建并返回
new MyPromise((resolve, reject) => {...})
必须封装
resolvePromise(promise2, x, resolve, reject)
辅助函数,统一处理
x
类型:
undefined
、普通值、
MyPromise
实例、带
then
方法的对象(thenable) 特别注意循环引用:
if (x === promise2)
时,必须
reject(new TypeError(...))
,这是 Promises/A+ 第 2.3.1 条硬性要求 不要省略
try/catch
onFulfilled
onRejected
抛错,必须立即用
reject(e)
向下游传递,这是错误穿透的基础 回调必须走微任务,
queueMicrotask
是首选 规范明确要求:
onFulfilled
onRejected
必须异步调用,且属于微任务(microtask),不是宏任务(macrotask)。用
setTimeout
setImmediate
会导致时序错误,
Promise.resolve().then
是兼容方案,但有额外开销。 实操建议: Node.js 11+ 和现代浏览器均支持
queueMicrotask
,优先使用它调度回调 若需兼容旧环境(如 IE),可用
Promise.resolve().then(() => {...})
模拟,但别用
setTimeout(..., 0)
注意:executor 内部是同步执行的,但注册到
onFulfilledCallbacks
数组里的回调,必须等 executor 执行完再进微任务队列 多个
then
注册的回调要按注册顺序执行,所以
onFulfilledCallbacks
onRejectedCallbacks
必须是数组,不能只存一个函数
catch
是语法糖,但错误穿透机制不能靠它实现
catch(onRejected)
确实只是
then(null, onRejected)
的简写,但它背后依赖的是
then
内部对回调异常的捕获与重抛。很多人以为“写了
catch
就万事大吉”,其实只要
then
里没做
try/catch
+
reject(e)
,错误就无法向下传递。 实操建议:
catch
方法体只需一行:
return this.then(null, onRejected);
真正关键的是
then
内部对
onFulfilled
onRejected
的包裹逻辑:必须用
try { const x = callback(value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); }
如果
onFulfilled
里抛错,这个错误会被下一个
then
onRejected
捕获,不是因为
catch
,而是因为上一个
then
返回的新 Promise 已被
reject
不要在
catch
回调里
throw
却不返回新 Promise,否则链式在此中断 最难的部分不是写代码,而是理解“每个
then
都在创建一个新 Promise,并把前一个的结果作为输入”这个抽象链条。状态、值、错误、微任务时机,四者耦合极紧,改一处就得通盘验证。Promises/A+ 测试套件跑不过,往往不是少写了一个
if
,而是某个微任务没对齐、某个拒绝没透传、或者循环引用没拦截。

相关文章