Generator模拟器核心是用闭环状态机精准建模yield暂停-恢复契约:每个yield对应唯一状态ID,记录求值、返回、resume目标及异常处理路径,分离闭包与执行上下文。

手写 Generator 编译器模拟器时,核心不是堆砌语法树或词法分析器,而是精准还原 JavaScript 引擎对 function* 和 yield 的状态调度契约。这个契约的本质是:**每个 yield 点必须对应一个明确的、可重入的暂停态,且恢复时必须严格回到该点之后的下一条执行位置,而非函数入口**。这无法靠简单 switch-case 模拟,必须用闭环状态机建模。
状态机必须显式编码“暂停-恢复”双向契约
Generator 的每次 next() 调用不是从头执行,而是从上次 yield 暂停处继续。状态机不能只定义“当前在哪”,还要定义“下次从哪 resume”。建议为每个 yield 表达式生成唯一状态 ID,并在状态转移表中同时记录:
- 进入该状态时要执行的副作用(如求值 yield 表达式、返回 value)
- 退出该状态时要跳转的目标状态(即下一次 resume 的入口)
- 是否为终止态(如 return 或 throw 后不可再 next)
例如:yield a; yield b; 对应三个状态:START → YIELD_A → YIELD_B → DONE,其中 YIELD_A 的 resume 目标是 YIELD_B,而非 START。
栈帧与上下文需分离存储,不可复用调用栈
真实 Generator 函数执行时,其闭包环境(自由变量)长期驻留,但执行上下文(如临时变量、操作数栈)在暂停时被冻结。模拟器中必须拆开这两层:
- 用独立对象保存闭包作用域(closure scope),生命周期=Generator 实例生命周期
- 每个状态节点附带一个轻量 context slot,仅存当前 yield 前后所需的局部变量快照(如 for 循环的 i、yield 表达式的求值中间结果)
- resume 时,不是“重新调用函数”,而是将对应 slot 加载到执行引擎的寄存器/操作栈中,接着执行下一条字节码
yield 表达式求值时机必须与状态绑定
yield 不是语句,而是表达式——它有返回值,且求值发生在暂停前、返回前。状态机中,YIELD_X 状态的处理逻辑必须是三段式:
- 计算 yield 右侧表达式(如 yield foo() 中先调用 foo)
- 将结果存入本次 next() 返回的 {value, done} 的 value 字段
- 将执行权交出,进入暂停态,等待 resume
若把 yield 当作纯跳转指令,就会漏掉表达式求值环节,导致与 V8/SpiderMonkey 行为不一致。
异常传播路径必须镜像引擎的 try-catch-yield 链
Generator 内部 throw 会触发状态机向最近的 catch 块跳转;外部 generator.throw(e) 则强制跳入当前暂停点的 try 区域。模拟器的状态转移表需额外维护:
- 每个状态关联的 nearest catch handler 状态 ID
- 外部 throw 时,不走常规 resume 路径,而是查表跳转到对应 handler 状态
- handler 执行完后,若未 re-throw,则按正常流程继续到下一个 yield 或 return

















