跳转到主内容
极星编程网:以代码为星,赴技术山海!

如何手动实现一个支持“状态快照”与“优先级调度”的基础 Promise 构造器

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,而不是空对象。这点常被忽略。

相关文章