Promise手动实现需支持状态快照、优先级调度与链式穿透:state为只读getter;resolve/reject后冻结value/reason;同步错误立即rejected;microtask内按priority升序执行回调;snapshot含timestamp、creationStack、handlersCount等调试信息;子Promise继承并下限父级priority,弱引用父快照。

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

















