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