> ## Content Index
> Fetch the complete content index at: https://blog.vercanti.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# JavaScript Generator 状态机原理
- URL: https://blog.vercanti.com/javascript-generator-zhuang-tai-ji-yuan-li/
- Published: 2026-08-28T14:34:56.000Z
- Updated: 2026-08-28T14:57:43.000Z
- Description: 核心矛盾： 开发者想用 async/await 写代码，但旧版 Chrome/Node 不支持。 Babel 的解决方案：把新语法"翻译"成等效的旧语法。 async/await 的底层是 Generator，所以必须先搞清楚 Generator。 普通函数：调用一次，运行到底，返回一个值。 Generator 函数：可以暂停，每次暂停时产出一个值，下次调用时从上次暂停的地方继续执行。 关键： Generator 函数被调用时不会立即执行，而是返回一个迭代器对象（iterator）。 每次调用 .next() 才会执行，直到下一个 yield 或函数结束
- Author: yellowdog
- Tags: js逆向, JS基础

> 官方文档：<https://tc39.es/ecma262/#sec-generatorfunction-objects>  
> 适用版本：ES2015+（2026-05-08 核实）  
> 理解 Babel 如何把 `async/await` 编译成你在混淆代码里看到的 `case 0: ... case 23:` 结构
> 
> 最后更新：2026-03-05（经官方文档复核）

---

## 一、为什么会有这个东西？

### 时间线

```
2009  Node.js 诞生，callback hell 开始肆虐
2012  Promise/A+ 规范发布，链式调用缓解了回调地狱
2015  ES2015 (ES6) 引入 Generator 函数
2016  Facebook 发布 regenerator，把 Generator 编译为 ES5
2017  ES2017 引入 async/await（语法糖，本质是 Generator + Promise）
2018  主流浏览器原生支持 async/await
至今  Babel 在构建时仍将 async/await 编译为 Generator 状态机（兼容旧环境）

```

**核心矛盾：** 开发者想用 `async/await` 写代码，但旧版 Chrome/Node 不支持。  
Babel 的解决方案：把新语法"翻译"成等效的旧语法。

> 参考：
> 
> - [TC39 Proposal: async functions](https://github.com/tc39/proposal-async-await)
> - [ECMAScript 2017 Language Specification §25.5](https://262.ecma-international.org/8.0/#sec-async-function-definitions)
> - [Facebook regenerator](https://github.com/facebook/regenerator)

---

## 二、先理解 Generator 函数（ES2015）

`async/await` 的底层是 Generator，所以必须先搞清楚 Generator。

### Generator 是什么？

普通函数：调用一次，运行到底，返回一个值。

Generator 函数：可以**暂停**，每次暂停时**产出一个值**，下次调用时从上次暂停的地方**继续执行**。

```js
function* counter() {
  console.log('start');
  yield 1;          // 暂停，产出 1
  console.log('resumed');
  yield 2;          // 暂停，产出 2
  console.log('end');
  return 3;         // 结束，产出 3
}

const gen = counter();     // 不会执行任何代码，只创建迭代器

gen.next();  // 执行到第一个 yield
// 打印: "start"
// 返回: { value: 1, done: false }

gen.next();  // 从上次暂停处继续
// 打印: "resumed"
// 返回: { value: 2, done: false }

gen.next();  // 继续执行到结束
// 打印: "end"
// 返回: { value: 3, done: true }

gen.next();  // 已经结束
// 返回: { value: undefined, done: true }

```

### Generator 怎么实现"暂停"？

**关键：** Generator 函数被调用时不会立即执行，而是返回一个**迭代器对象（iterator）**。  
每次调用 `.next()` 才会执行，直到下一个 `yield` 或函数结束。

可以把 Generator 理解成一个**可以暂停的状态机**：

```
状态 0 (初始)
    ↓ .next()
执行到第一个 yield，暂停
    ↓ .next()
从暂停处继续，执行到第二个 yield，暂停
    ↓ .next()
从暂停处继续，执行到 return，结束

```

> 参考：
> 
> - [MDN: Generator](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global%5FObjects/Generator)
> - [ECMAScript 2015 §25.3: Generator Objects](https://262.ecma-international.org/6.0/#sec-generator-objects)

---

## 三、async/await 是 Generator + Promise 的语法糖

```js
// async/await 写法
async function fetchUser() {
  const resp = await fetch('/api/user');
  const data = await resp.json();
  return data;
}

```

等价于（手动展开）：

```js
// Generator + Promise 手动实现
function fetchUser() {
  return run(function*() {
    const resp = yield fetch('/api/user');   // yield 代替 await
    const data = yield resp.json();
    return data;
  });
}

// 驱动函数：自动把 Generator 的 yield 和 Promise 连起来
function run(genFn) {
  return new Promise((resolve, reject) => {
    const gen = genFn();

    function step(nextValue) {
      let result;
      try {
        result = gen.next(nextValue);  // 推进 generator
      } catch (e) {
        return reject(e);
      }

      if (result.done) {
        return resolve(result.value);  // generator 跑完了，resolve
      }

      // generator 还没跑完，等待 yield 出来的 Promise
      Promise.resolve(result.value).then(step, reject);
      //                                 ↑
      //              Promise 完成后，把结果传回 generator，继续执行
    }

    step(undefined);  // 启动
  });
}

```

**每个 `await` 本质上是：**

1. `yield` 出一个 Promise，暂停函数执行
2. 等 Promise resolve 之后，把结果用 `.next(result)` 传回 Generator，继续执行

> 参考：
> 
> - [Jake Archibald: Async functions - making promises friendly](https://web.dev/articles/async-functions)
> - [Axel Rauschmayer: Exploring ES2016 and ES2017 - Async Functions](https://exploringjs.com/es2016-es2017/ch%5Fasync-functions.html)

---

## 四、Babel 如何编译 async/await（regenerator 变换）

Babel 使用 `@babel/plugin-transform-async-to-generator` \+ `regenerator-runtime` 把 `async/await` 编译为 **ES5 兼容**的代码。

核心思路：**把函数体转换为一个 `switch-case` 状态机**，用一个变量记录当前执行到了哪一步。

### 一个简单的例子

**原始代码：**

```js
async function example(url) {
  const resp = await fetch(url);
  const data = await resp.json();
  return data.name;
}

```

**编译后（精简注释版）：**

```js
function example(url) {
  return _asyncToGenerator(function* () {
    var resp, data;

    return regeneratorRuntime.wrap(function _callee$(ctx) {
      while (1) {
        switch (ctx.prev = ctx.next) {

          case 0:
            ctx.next = 2;
            return fetch(url);         // yield fetch(url)，暂停

          case 2:
            resp = ctx.sent;           // 接收 fetch 的结果
            ctx.next = 5;
            return resp.json();        // yield resp.json()，暂停

          case 5:
            data = ctx.sent;           // 接收 json() 的结果
            return ctx.abrupt("return", data.name); // return data.name

          case 7:
          case "end":
            return ctx.stop();
        }
      }
    }, _callee);
  })();
}

```

### 状态机变量对照表

| 变量名                        | 含义                                   |
| -------------------------- | ------------------------------------ |
| ctx.next / e.next          | 下一步要执行的 case 编号                      |
| ctx.prev / e.prev          | 上一步执行的 case 编号（用于 try-catch）         |
| ctx.sent / e.sent          | 上一个 yield 的返回值（即 Promise resolve 的值） |
| ctx.t0, ctx.t1...          | 临时变量（原始代码里的中间结果）                     |
| ctx.abrupt("return", v)    | 对应 return v                          |
| ctx.abrupt("break", label) | 对应 break                             |
| ctx.stop()                 | 函数执行结束                               |
| return ctx.next=N, expr    | await expr：把 next 设为 N，yield 出 expr  |

---

## 五、逐行解读你在逆向中遇到的代码

### 你实际看到的代码（原始）

```js
function N(){
  return (N = (0,o.Z)(s().mark(
    (function e(t) {
      var i,o,a,c,f,d,h,p,v,m,w,x,C,R;
      return s().wrap((function(e){
        for(;;) switch(e.prev = e.next){
          case 0:
            return e.next=2, P(t);
          case 2:
            i=e.sent, o=i.url, ...
            c=(0,r.Z)(i,k),
            f="string"===typeof t ? t : t.url,
            ...
          case 9:
            if(!(0,O.ut)(f)){ e.next=23; break }
            return e.next=16, Promise.resolve().then(n.bind(n,1218));
          case 16:
            m=e.sent, w=m.changeURLOrigin,
            x=m.mergeRequestSharedParams,
            C=m.mergeRequestSharedHeader,
            c.body=x(f,c),
            c.headers=C(f,c.headers),
            o=w(f,o,c);
          case 23:
            R=null===c||void 0===c?void 0:c._res,
            ...
            return e.abrupt("return", fetch(o,c).then(...));
          case 26:
          case "end":
            return e.stop()
        }
      }), e)
    })
  ))).apply(this,arguments)
}

```

### 还原后的 async/await 版本（逐步分析）

**变量解码：**

| 混淆变量 | 实际含义                        | 推断依据                                    |
| ---- | --------------------------- | --------------------------------------- |
| t    | 请求参数（URL 字符串或配置对象）          | 函数入参                                    |
| i    | await P(t) 的结果（处理后的请求配置）    | case 2: i = e.sent                      |
| o    | 最终请求 URL                    | o = i.url                               |
| c    | 请求 config（headers、body 等）   | fetch(o, c)                             |
| f    | 原始 URL（用于判断接口类型）            | f = typeof t === 'string' ? t : t.url   |
| C    | mergeRequestSharedHeader 函数 | case 16: C = m.mergeRequestSharedHeader |
| R    | 响应处理器                       | R = c.\_res                             |

**还原：**

```js
async function sendRequest(requestConfig) {

  // case 0~2: await P(requestConfig) → 预处理请求（建 headers、转换 URL）
  const prepared = await P(requestConfig);

  const finalUrl   = prepared.url;
  const config     = omit(prepared, ['url', 'noTextReplace']);
  const originalUrl = typeof requestConfig === 'string'
                      ? requestConfig
                      : requestConfig.url;

  // 设置超时（400ms 或自定义）
  const timeout    = requestConfig?.timeout ?? 400;
  let   isTimeout  = false;
  const controller = new AbortController();
  const timer      = setTimeout(() => { isTimeout = true; controller.abort(); }, timeout);
  config.signal    = config.signal || controller.signal;

  // case 9~23: 如果是特殊接口（/api/mobile, /yewu 等），合并共享参数和 headers
  if (isSpecialUrl(originalUrl)) {
    // case 9~16: 动态加载共享模块（chunk 1218）
    const sharedModule = await import(/* chunk 1218 */ './sharedModule');

    const { changeURLOrigin, mergeRequestSharedParams, mergeRequestSharedHeader } = sharedModule;

    config.body    = mergeRequestSharedParams(originalUrl, config);
    config.headers = mergeRequestSharedHeader(originalUrl, config.headers);
    finalUrl       = changeURLOrigin(originalUrl, finalUrl, config);
  }

  // case 23: 真正发出请求
  const customResponseHandler = config._res;
  delete config._res;

  return fetch(finalUrl, config)          // ← 调用栈指向这里
    .then(response => {
      if (!response.ok) throw new HttpError({ status: response.status, ... });
      // 解析响应...
    })
    .catch(err => { ... })
    .finally(() => clearTimeout(timer));
}

```

---

## 六、状态机的核心模式速查

掌握这几个模式，95% 的编译代码都能读懂：

### 模式 1：`await` 表达式

```js
// 原始
const result = await somePromise();

// 编译后
case N:
  return e.next = N+2, somePromise();   // yield，暂停
case N+2:
  result = e.sent;                      // 接收结果，继续

```

### 模式 2：`return` 语句

```js
// 原始
return someValue;

// 编译后
return e.abrupt("return", someValue);

```

### 模式 3：`if` 条件（条件为假时跳过）

```js
// 原始
if (condition) {
  // block A (cases 10-15)
}
// block B (case 16)

// 编译后
case 9:
  if (!condition) { e.next = 16; break }  // 条件为假 → 跳到 16
  // ... block A 的 cases
case 16:
  // block B

```

### 模式 4：`try-catch`

```js
// 原始
try {
  const x = await riskyOp();
} catch (err) {
  handle(err);
}

// 编译后
case N:
  e.prev = N;             // 记录 try 块起点（用于找 catch）
  return e.next = N+2, riskyOp();
case N+2:
  x = e.sent;
  e.next = N+6; break;    // try 正常结束 → 跳过 catch
case N+4:
  e.prev = N+4;           // catch 块
  err = e.t0;
  handle(err);
case N+6:
  // 后续代码

```

### 模式 5：临时变量 `e.t0`, `e.t1`...

```js
// 原始（三元表达式含 await）
const x = condition ? await a() : await b();

// 编译后
case N:
  if (!condition) { e.next = N+4; break }
  return e.next = N+3, a();
case N+3:
  e.t0 = e.sent; e.next = N+6; break;
case N+4:
  return e.next = N+6, b();
case N+6:
  x = e.t0;

```

---

## 七、`s()` 和 `o.Z` 是什么？

你在真实代码里还会看到这些：

```js
// 代码里的
var s = n(87794);    // 87794 是 regenerator-runtime
s = n.n(s);          // .n() 是 webpack 的 ESM/CJS 互操作包装

return (N = (0, o.Z)(s().mark(function e(t) { ... })))

```

对照关系：

| 混淆写法               | 原始含义                                          |
| ------------------ | --------------------------------------------- |
| s()                | regeneratorRuntime（全局运行时）                     |
| s().mark(fn)       | regeneratorRuntime.mark(fn)：标记一个 generator 函数 |
| s().wrap(fn, self) | regeneratorRuntime.wrap(fn, self)：创建状态机执行器    |
| o.Z                | asyncToGenerator（Babel 辅助函数）                  |
| (0, o.Z)(...)      | 调用 asyncToGenerator，(0, fn)() 是防止 this 绑定的写法  |

完整的包装层次：

```
asyncToGenerator(                        ← 把 Generator 包装成 Promise
  regeneratorRuntime.mark(               ← 标记这是一个 Generator 函数
    function*(t) {
      return regeneratorRuntime.wrap(    ← 创建状态机
        function(ctx) {
          switch(ctx.prev = ctx.next) {
            // 状态机 cases
          }
        },
        _callee                          ← Generator 函数自身的引用
      )
    }
  )
)

```

> 参考：
> 
> - [regenerator-runtime 源码](https://github.com/facebook/regenerator/blob/main/packages/runtime/runtime.js)
> - [Babel @babel/plugin-transform-async-to-generator](https://babeljs.io/docs/babel-plugin-transform-async-to-generator)
> - [Babel @babel/plugin-transform-regenerator](https://babeljs.io/docs/babel-plugin-transform-regenerator)

---

## 八、自己动手实验

最好的学习方式是用 Babel REPL 自己编译，观察变化。

### 方法 A：Babel 在线 REPL

网址：**<https://babeljs.io/repl>**

左侧 Presets 勾选 `env`，然后粘贴：

```js
async function example(url) {
  const resp = await fetch(url);
  const data = await resp.json();

  if (data.ok) {
    const extra = await fetchExtra();
    return { data, extra };
  }

  return { data };
}

```

右侧立刻看到编译结果，对照本文档理解每一行。

### 方法 B：本地编译

```bash
# 安装
npm install --save-dev @babel/core @babel/plugin-transform-async-to-generator \
                       @babel/plugin-transform-regenerator \
                       regenerator-runtime

# 新建 .babelrc
echo '{"plugins": ["@babel/plugin-transform-async-to-generator",
                   "@babel/plugin-transform-regenerator"]}' > .babelrc

# 编译
npx babel input.js -o output.js

```

### 方法 C：Chrome DevTools 实时观察

1. 在控制台定义一个 async 函数
2. 打断点，调用它
3. 在 Sources 面板的 Scope 区域观察 `ctx` / `e` 对象的值变化

---

## 九、总结

```
你写的:           async function f() { const x = await p(); return x; }
                              ↓ Babel 编译
Generator 函数:   function* f() { const x = yield p(); return x; }
                              ↓ regenerator-runtime 变换
状态机:           switch(ctx.next) {
                    case 0: return ctx.next=2, p();
                    case 2: x = ctx.sent; return ctx.abrupt("return", x);
                  }
                              ↓ 你在逆向里看到的
混淆版:           switch(e.prev=e.next){case 0:return e.next=2,p(t);
                  case 2:i=e.sent;return e.abrupt("return",i)}

```

**读懂的关键只有三条：**

1. `return e.next=N, expr` \= `await expr`（暂停，下一步去 case N）
2. `e.sent` \= 上一个 await 的结果
3. `e.abrupt("return", v)` \= `return v`

---

## 最佳实践

**识别状态机的关键特征是 `switch(e.prev = e.next)`**：Babel 编译的 async 函数基本结构固定，`e.prev`（上一个 case）和 `e.next`（下一个目标 case）是状态机跳转的核心；看到这对变量可以快速确认是编译后的 Generator。

**通过 `case` 数字还原 `await` 结构**：每个 `await` 对应两个相邻 `case`：第一个 `case` 触发异步操作并设置 `e.next`，下一个 `case` 接收结果（`e.sent`）。数一数有多少对 case 就知道有多少个 await。

**逆向时优先找 `_asyncToGenerator` 或 `regeneratorRuntime`**：这两个是 Babel 编译 async 函数的运行时辅助函数，全局搜索这些关键字可以快速定位文件中所有的 async 函数入口。

**`e.abrupt("return", v)` 等价于 `return v`**：Babel 用 `e.abrupt` 系列方法替代了 `return`/`break`/`continue`；看到这些方法调用时直接在脑中翻译为对应的控制流关键字。

**用 babel 反编译验证理解**：对混淆代码的某段状态机，用 `@babel/parser` 解析后用 `@babel/plugin-transform-async-to-generator` 的逆过程（或直接用新版 Babel 编译等价的 async 代码），对比结构确认是否理解正确。

---

## 常见陷阱

### 陷阱：`e.sent` 是 `undefined`，以为是 `await` 没有返回值

**现象：** 在断点中看到 `e.sent` 是 `undefined`，以为被 await 的 Promise 没有 resolve 值。  
**原因：** `e.sent` 在第一个 `case`（触发异步操作时）确实是 `undefined`；需要在设置了 `return` 语句的 `case` 执行之后，下一个 `case` 开始时才能拿到 `e.sent` 的值。  
**解决：** 断点设在 `e.next = X` 之后的 `case X:` 开头，此时 `e.sent` 才是 await 的结果。

### 陷阱：把 `_asyncToGenerator` 的闭包层误认为业务代码

**现象：** 进入 async 函数后堆栈出现多层函数（`_asyncToGenerator`、`_callee$`、`step`），不知道哪一层是实际业务逻辑。  
**原因：** Babel 把每个 async 函数包裹在 `_asyncToGenerator(function* _callee$() {...})` 中，`_asyncToGenerator` 负责运行 Generator，`_callee$` 才是实际逻辑。  
**解决：** 直接在 Call Stack 中点击 `_callee$` 函数帧，跳到实际业务逻辑层。

### 陷阱：`e.abrupt("throw", err)` 混淆了 return 和 throw

**现象：** 看到 `e.abrupt("return", ...)` 以为函数 return，实际上是 `e.abrupt("throw", ...)` 抛出了异常。  
**原因：** Babel 将 `throw` 编译为 `e.abrupt("throw", err)`，`try/catch` 对应 `e.prev = try_start, e.stop = catch_start`；字面量相似，不仔细看容易混淆。  
**解决：** 记忆区分：`"return"` \= 函数返回；`"throw"` \= 抛出异常；`"break"` \= 循环 break；`"continue"` \= continue。

---

## 参见

[JavaScript Promise 完全指南](https://blog.vercanti.com/javascript-promise-wan-quan-zhi-nan/)  
[混淆还原](https://blog.vercanti.com/hun-yao-huan-yuan/)  
[Babel AST入门](https://blog.vercanti.com/babel-ast-ru-men/)