手写 async/await 编译器模拟器的核心是复现引擎内部基于状态机的执行跳转逻辑:将每个 await 拆为暂停点与恢复入口,用 state + switch 控制流,固化局部变量、待求值表达式及下条语句位置,错误传播与 try/catch 需精确映射,且 await 与 yield 状态必须分离。

手写 async/await 编译器模拟器的核心,不是模拟语法糖,而是精准复现 JavaScript 引擎内部基于状态机的执行跳转逻辑。关键在于把每个 await 拆解为「暂停点」和「恢复入口」,用显式状态变量 + switch 分发控制流,而非依赖 Promise.then 链式回调。
状态机必须捕获三个核心上下文
每次 await 暂停时,需固化当前函数的执行现场:
- 局部变量快照:所有非 const 声明的变量(包括参数)必须保存到闭包或状态对象中,不能依赖作用域链自动保留
- await 表达式待求值:不是直接执行 await 后的表达式,而是记录其生成器/函数引用,在 RESUME 状态再调用并传入上一个 Promise 结果
- 下一条语句位置:用整数 state 编号标记 resume 后该从哪行继续(如 state = 3 表示跳转到第 3 个 await 后的赋值语句)
跳转逻辑靠 state + switch 驱动,不靠嵌套 then
每个 async 函数编译后应是一个带 next() 方法的对象,内部 switch 根据当前 state 决定行为:
- state === 0:初始化,执行首段同步代码,遇到第一个 await → 保存上下文,返回 { value: promise, done: false },state 设为 1
- state === 1:收到上一个 promise 的 fulfilled 值 → 恢复变量,执行 await 后语句,遇到下一个 await → state 设为 2,返回新 promise
- state === -1:表示已完成,返回 { value: finalResult, done: true }
注意:resume 不是重新执行整个函数,而是从对应 state 分支进入,跳过已执行的同步块。
错误传播必须与原生行为一致
reject 不应只触发 catch 块,还要影响后续状态流转:
- 任何 await 后的 promise reject,必须立刻将 state 设为 -1,并把 error 存入 context.error
- next() 被调用时若 context.error 存在,直接 throw 它,且不再执行 switch 中的其他 case
- try/catch 块需编译为额外的 state 区间(如 try-start: state=5, catch-start: state=6),并在 reject 时跳转至对应 catch 入口
yield 和 await 的状态映射要分离
不要混用 Generator 的 yield 和 await 的暂停语义:
- async 函数本质是 Promise 状态机,yield 是迭代器协议,二者 runtime 机制不同
- 若模拟器同时支持 async 和 generator,await 暂停必须走 Promise resolve/reject 流程,yield 暂停走 iterator.next(value) 流程
- 同一个函数体内出现 await 和 yield 时,state 编号需按语法顺序线性递增,但每个 await 对应一个 Promise 链节点,每个 yield 对应一个迭代步,不可共用同一组 resolve 回调











