侧边栏壁纸
博主头像
一笑痕

人生若只如初见,
是可喜亦或者是可悲?

  • 累计撰写 259 篇文章
  • 累计收到 7 条评论

resolve 收下 thenable,reject 已无效

2026-11-4 / 0 评论 / 4 阅读

resolve 收下 thenable,reject 已无效

把回调式接口改成 Promise 时,常见做法是在外层保存 resolve 和 reject,等事件回来再调用。Promise.withResolvers() 把这两个函数和对应的 promise 一起返回,省掉了构造器里的外层变量。它看起来像一对随时可按的按钮,实际仍受 Promise 的一次性定稿规则约束。

本机 Node.js v26.8.1 的测试把 thenable 交给 resolve,紧接着又调用 reject。调用刚结束时 promise 的反应函数还没运行,thenable 的 then 也尚未执行;等 await 取值,结果已经是 thenable 提供的 adopted。后面的拒绝没有改变结果。

resolver 交出来,不代表 promise 可重复使用

Promise.withResolvers() 返回的对象有 promise、resolve 和 reject 三个属性。resolver 可以保存在外层,供事件回调或稍后的代码使用;创建出来的 promise 仍遵守普通 Promise 的状态规则。

const { promise, resolve, reject } = Promise.withResolvers();

resolve('done');
reject(new Error('too late'));

console.log(await promise); // done

对同一组 resolver 连续调用,第一次确定的结果会保留。resolve('done') 之后再调用 resolve('again') 或 reject(error),都不会把已经选定的结果改掉。拆开写只改变了 resolver 的位置,没有给 promise 增加重置能力。

thenable 的 then 不是在 resolve 这一行里执行

传给 resolve 的值若是普通字符串,promise 会以该值兑现。若传入一个 thenable,Promise 会采用它的结果;但读取并调用 thenable 的 then 不会同步插在 resolve(value) 这一行里。Node 实测的调用顺序是:resolver 调用先返回,thenable 的 then 随后运行,promise 的回调再运行。

这解释了一个容易误读的输出:紧接 resolve(thenable) 打印状态,仍可能看到回调尚未执行。不能因此断定后面的 reject() 还有机会获胜。resolver 一旦接收了 thenable,后续对同一 promise 的 reject 已经无效;兑现值要等 thenable 的结果被采用后才传到 await 或 .then() 回调。

下面是实际日志。事件顺序里的第一项来自同步代码,后两项分别是 thenable 与 promise 的后续处理:

state immediately after resolver calls: pending
thenable calls before await: 0
awaited value: adopted
thenable calls after await: 1
final state: fulfilled:adopted
event order: ["after resolve and reject calls","thenable called","promise reaction:adopted"]
repeated resolver calls keep: first
assertions: passed

这里的 pending 描述的是回调尚未观察到最终状态,不表示 resolver 还可以重新选结果。把「回调什么时候运行」和「结果是否已被定稿」混成一个概念,就会把异步调度误认为可以撤销。

外部 resolver 的边界由谁持有

withResolvers 适合把 Promise 的完成权交给另一处代码,例如事件到达时结束等待。保存 resolver 的对象也就拥有了完成这次等待的能力。若它被放进长期存活的全局对象,短命任务结束后还留着引用,后来误触发时调用虽不会改写已完成的 promise,却会让生命周期和资源归属更难读。

这组 API 不负责取消事件监听,也不会替等待中的任务设超时。若等待需要取消,取消信号或明确的清理函数仍得另行设计。resolver 只负责兑现或拒绝一次;超时与取消属于另外的状态转换,不能靠重复调用 reject 假装成可逆控制。

我会把 resolver 交给真正掌握完成时机的那一层,并在任务结束后清掉外部引用。只有调用点确实分散、又需要同一个 promise 时,withResolvers 才比构造器里的闭包更清楚;若 resolve 和 reject 本来就在构造函数附近,传统 new Promise 更容易看出谁能结束它。

    🤞 分享