Promise手动实现需支持状态快照、优先级调度与链式穿透:state为只读getter;resolve/reject后冻结value/reason;同步错误立即rejected;microtask内按priority升序执行回调;snapshot含timestamp、creationStack、handlersCount等调试信息;子Promise继承并下限父级priority,弱引用父快照。
Promise 构造器必须自己管理 pending/fulfilled/rejected 三种状态
原生
的状态不可逆(一旦变为
或
就不能再改),但“状态快照”要求能随时读取当前状态,甚至在未 settle 前保留中间态。手动实现时不能依赖闭包隐藏状态,而要显式暴露一个只读的
属性(值为
/
/
)和可选的
或
。
关键点:
必须是 getter,且不可写;直接赋值会绕过状态迁移逻辑
调用
或
后,需立即冻结
/
(用
防止后续篡改影响快照一致性)
若构造函数中同步抛错(如
),应直接进入
态,而非留待异步处理
优先级调度靠 microtask 队列 + 显式 priority 字段控制执行顺序
原生
回调总在当前任务末尾以 microtask 执行,但“优先级调度”意味着高优回调可以插队——比如 UI 渲染前必须先处理某个
。手动实现时,不能只靠
,而要在内部维护一个按
排序的回调队列(例如用最小堆或简单数组 +
)。
实操建议:
每个
注册时接受可选
(数字,默认
;越小越优先)
所有回调统一进 microtask,但在 microtask 内部按 priority 升序执行,避免跨 tick 调度导致竞态
注意:Chrome/Firefox 对
的调用频次有限制,高频插入高优回调时,应合并同 priority 的回调为单个 microtask
快照数据必须包含时间戳与调用栈上下文
仅记录
不够,“快照”要能辅助调试:比如某个 Promise 卡在 pending 5 秒,你得知道它是在哪行创建、谁触发了 resolve、是否被多次
。因此快照对象至少含:
、
、
(
截取)、
(如果已 resolve)、
(当前注册的 then 回调数)。
容易踩的坑:
不要在构造函数里直接捕获
,V8 可能优化掉 stack trace;应使用
或兜底
用
而非
,避免系统时钟回拨干扰耗时判断
快照方法(如
)应返回新对象,不暴露内部引用,防止外部修改污染状态一致性
链式调用中优先级与快照需穿透到子 Promise
当
时,子 Promise 的快照应能追溯父级状态,且子级
的 priority 应默认继承父级(除非显式覆盖)。否则高优父 Promise 的后续逻辑可能被低优子回调拖慢。
具体做法:
每个子 Promise 实例保存
(弱引用父快照对象),避免内存泄漏
返回的新 Promise 初始化时,将
设为
,保证“优先级不升反降”原则
子 Promise 的
方法自动合并父快照字段(如
),但不递归深拷贝,只存浅引用
最麻烦的是错误传播路径的快照关联——
回调触发的 reject 若没带新 reason,快照里的
应指向原始 rejection 的 error,而不是空对象。这点常被忽略。
Promisefulfilledrejectedstate"pending""fulfilled""rejected"valuereasonstateresolve()reject()valuereasonObject.freezethrow new Error("init failed")rejectedPromise.thenthenqueueMicrotaskprioritysortthenoptions.priority0queueMicrotaskstatethenstatetimestampcreationStacknew Error().stackresolvedAthandlersCounterror.stackError.captureStackTrace?.(this, MyPromise)new Error().stacktimestampperformance.now()Date.now()snapshot()promise.then(onFulfill).then(...)thenparentSnapshotRefthenpriorityMath.min(parentPriority, explicitPriority)snapshot()ancestors: [parentSnap, grandParentSnap]catchreason