Generator 自动执行器 (run 函数) 深度解析

概述

run 函数是一个 Generator 自动执行器,用于自动驱动 Generator 函数执行,让异步代码可以用同步的方式编写。它是 async/await 出现之前,JavaScript 社区处理异步流程的重要模式。

核心代码

function run(gen) {
  var args = [].slice.call(arguments, 1);
  var it = gen.apply(this, args);

  return new Promise(function(resolve, reject) {
    function handleNext(value) {
      try {
        var next = it.next(value);
        handleResult(next);
      } catch (e) {
        reject(e);
      }
    }

    function handleResult(next) {
      if (next.done) {
        resolve(next.value);
      } else {
        Promise.resolve(next.value)
          .then(handleNext)
          .catch(function(err) {
            try {
              handleResult(it.throw(err));
            } catch (e) {
              reject(e);
            }
          });
      }
    }

    handleNext();
  });
}

一、设计原理与思想

1.1 Generator 与异步的关系

Generator 函数有两个关键特性使其适合处理异步:

特性 说明 异步应用
可暂停执行 yield 会暂停函数,交出控制权 等待异步操作完成
双向通信 yield 接收外部传入的值 异步结果回传给 Generator
function* main() {
  // yield 暂停,等待 Promise 完成
  // 完成后,结果通过 it.next(value) 传回
  let a = yield fetchData(10, 100);
  //                        ↑           ↑
  //                  yield 出 Promise  结果传回给 a
}

1.2 自动执行器的核心任务

执行器需要解决三个核心问题:

  1. 自动推进:检测 yield 出的 Promise,等待完成后自动继续
  2. 值传递:将 Promise 的 resolve 值传回 Generator
  3. 异常传递:将 Promise 的 reject 错误抛回 Generator

1.3 为什么需要 Promise 包装?

return new Promise(function(resolve, reject) { ... });
  • 统一返回值类型,调用者可以用 .then() 获取结果
  • 将内部异常正确传递给外部
  • 支持 async/await 调用:await run(main)

二、核心函数深度解析

2.1 初始化阶段

var args = [].slice.call(arguments, 1);
var it = gen.apply(this, args);

参数处理解析:

// 支持向 Generator 传参
run(main, arg1, arg2, arg3);

// 等价于
main(arg1, arg2, arg3);

为什么用 gen.apply(this, args)

  • apply 可以接受数组形式的参数
  • 保持 this 上下文(虽然通常用不到)

2.2 handleNext 函数详解

function handleNext(value) {
  try {
    var next = it.next(value);
    handleResult(next);
  } catch (e) {
    reject(e);
  }
}

参数 value 的来源:

调用时机 value 值
首次调用 handleNext() undefined(无参数)
Promise resolve 后 Promise 的 resolve 值

try-catch 的作用:

捕获 it.next(value) 可能抛出的异常:

function* main() {
  throw new Error("同步错误");
  yield Promise.resolve(1);
}

// it.next() 会直接抛出 Error("同步错误")

2.3 handleResult 函数详解

function handleResult(next) {
  if (next.done) {
    resolve(next.value);
  } else {
    Promise.resolve(next.value)
      .then(handleNext)
      .catch(function(err) {
        try {
          handleResult(it.throw(err));
        } catch (e) {
          reject(e);
        }
      });
  }
}

Promise.resolve(next.value) 的妙用:

// 情况1:yield 出的是 Promise
yield fetchData(10, 100);
// Promise.resolve(promise) === promise(原样返回)

// 情况2:yield 出的是普通值
yield 42;
// Promise.resolve(42) 创建一个 resolve(42) 的 Promise

// 好处:统一处理,无需判断类型

递归调用分析:

handleResult(next)
    │
    └── .then(handleNext)
            │
            └── it.next(value)
                    │
                    └── handleResult(next)
                            │
                            └── ... 循环继续

三、异常处理机制深度解析

3.1 异常传播的完整路径

Promise reject
      │
      ▼
.catch(function(err) { ... })
      │
      ▼
it.throw(err)
      │
      ├──▶ Generator 内部 try-catch 捕获
      │          │
      │          ├── 继续执行 ──▶ { done: false, value: ... }
      │          │
      │          └── 返回 ──▶ { done: true, value: ... }
      │
      └──▶ Generator 未捕获
               │
               ▼
          抛出异常
               │
               ▼
          try-catch 捕获 ──▶ reject(e)

3.2 三种异常场景详解

场景1:捕获后继续执行
function* main() {
  let a;
  try {
    a = yield Promise.reject("错误1");
  } catch (err) {
    console.log("捕获:", err);
    a = 100;  // 恢复执行,给默认值
  }
  // 继续执行后续代码
  let b = yield Promise.resolve(a * 2);
  return b;
}

// 执行过程:
// 1. it.next() → { done: false, value: Promise.reject("错误1") }
// 2. Promise reject → .catch 捕获
// 3. it.throw("错误1") → Generator catch 捕获,a = 100
// 4. 继续执行到下一个 yield → { done: false, value: Promise.resolve(200) }
// 5. Promise resolve → handleNext(200)
// 6. it.next(200) → { done: true, value: 200 }
// 7. resolve(200)
场景2:捕获后返回
function* main() {
  try {
    yield Promise.reject("错误2");
  } catch (err) {
    return "降级结果";  // 提前返回
  }
  // 这里的代码不会执行
  yield Promise.resolve("不会到达");
}

// 执行过程:
// 1. it.next() → { done: false, value: Promise.reject("错误2") }
// 2. Promise reject → .catch 捕获
// 3. it.throw("错误2") → Generator catch 捕获,return "降级结果"
// 4. 返回 { done: true, value: "降级结果" }
// 5. handleResult → resolve("降级结果")
场景3:未捕获异常
function* main() {
  // 没有 try-catch
  let a = yield Promise.reject("未捕获错误");
  return a;
}

// 执行过程:
// 1. it.next() → { done: false, value: Promise.reject("未捕获错误") }
// 2. Promise reject → .catch 捕获
// 3. it.throw("未捕获错误") → Generator 内部抛出异常
// 4. try-catch 捕获 → reject("未捕获错误")

3.3 为什么用 handleResult 处理 it.throw(err)?

核心原因:it.throw(err) 返回迭代器对象,不是只抛异常

// it.throw(err) 的返回值类型
{ done: boolean, value: any }

// 与 it.next() 返回值类型完全相同
{ done: boolean, value: any }

设计意义:

Generator 的异常处理是"可恢复的"。即使发生错误,Generator 也可以:

  • 捕获错误后继续执行(返回 { done: false }
  • 捕获错误后返回结果(返回 { done: true }

因此需要用 handleResult 统一处理这两种情况。


四、执行流程可视化

4.1 完整执行流程图

┌─────────────────────────────────────────────────────────────────┐
│                         run(main)                               │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
                    ┌─────────────────┐
                    │   handleNext()  │◄─────────────────────┐
                    └─────────────────┘                      │
                              │                              │
                              ▼                              │
                    ┌─────────────────┐                      │
                    │  it.next(value) │                      │
                    └─────────────────┘                      │
                              │                              │
                              ▼                              │
                    ┌─────────────────┐                      │
                    │ handleResult()  │                      │
                    └─────────────────┘                      │
                              │                              │
              ┌───────────────┴───────────────┐              │
              │                               │              │
              ▼                               ▼              │
     ┌─────────────────┐            ┌─────────────────┐      │
     │   done: true    │            │   done: false   │      │
     └─────────────────┘            └─────────────────┘      │
              │                               │              │
              ▼                               ▼              │
     ┌─────────────────┐            ┌─────────────────┐      │
     │ resolve(value)  │            │ Promise.resolve │      │
     │                 │            │   (next.value)  │      │
     └─────────────────┘            └─────────────────┘      │
              │                               │              │
              ▼                      ┌────────┴────────┐     │
        ┌──────────┐                 │                 │     │
        │   结束   │                 ▼                 ▼     │
        └──────────┘        ┌─────────────┐   ┌─────────────┐│
                            │  .then(     │   │  .catch(    ││
                            │ handleNext) │   │  err处理)   ││
                            └─────────────┘   └─────────────┘│
                                    │                 │      │
                                    │                 ▼      │
                                    │        ┌─────────────┐ │
                                    │        │it.throw(err)│ │
                                    │        └─────────────┘ │
                                    │                 │      │
                                    │                 ▼      │
                                    │        ┌─────────────┐ │
                                    │        │handleResult │ │
                                    │        └─────────────┘ │
                                    │                 │      │
                                    └─────────────────┼──────┘
                                                      │
                                    ┌─────────────────┘
                                    │
                                    ▼
                              (循环继续...)

4.2 时序图

run()          handleNext()      it.next()      handleResult()     Promise
  │                 │                │                │               │
  │──handleNext()──▶│                │                │               │
  │                 │──it.next()────▶│                │               │
  │                 │                │──{done,value}─▶│               │
  │                 │                │                │──resolve()───▶│
  │                 │                │                │               │
  │                 │                │                │◀──value───────│
  │                 │◀──handleNext()─│◀───────────────│               │
  │                 │                │                │               │
  │                 │──it.next(val)─▶│                │               │
  │                 │                │──{done,value}─▶│               │
  │                 │                │                │──resolve()───▶│
  │                 │                │                │               │
  │                 │                │                │◀──value───────│
  │                 │                │                │               │
  │                 │                │                │──done:true───▶│
  │◀─────────────────────────────────────────────────resolve(value)   │
  │                                                                      │

五、与 async/await 对比

5.1 代码对比

使用 run + Generator:

function* main() {
  let a = yield fetchData(10, 100);
  let b = yield fetchData(a, 100);
  return b;
}

run(main).then(result => console.log(result));

使用 async/await:

async function main() {
  let a = await fetchData(10, 100);
  let b = await fetchData(a, 100);
  return b;
}

main().then(result => console.log(result));

5.2 功能对比表

特性 run + Generator async/await
语法 yield await
函数声明 function* async function
返回值 需要包装 自动返回 Promise
错误处理 需要 run 函数支持 原生支持
浏览器支持 ES6 (2015) ES8 (2017)
调试体验 较差 更好的堆栈跟踪
性能 略低 更优

5.3 async/await 的本质

async/await 可以理解为 Generator + 自动执行器的语法糖:

// async/await 本质上等价于
async function main() {
  let a = await fetchData(10, 100);
  return a;
}

// 等价于
function* main() {
  let a = yield fetchData(10, 100);
  return a;
}
run(main);

六、边界情况处理

6.1 yield 非 Promise 值

function* main() {
  let a = yield 42;        // 普通值
  let b = yield "hello";   // 字符串
  let c = yield null;      // null
  return [a, b, c];
}

// Promise.resolve(42) 会将其包装为 Promise
// 结果: [undefined, undefined, undefined]
// 因为普通值没有 resolve 值传递

6.2 空 Generator

function* main() {
  // 空函数
}

run(main).then(result => {
  console.log(result);  // undefined
});

6.3 同步返回

function* main() {
  return "直接返回";
  yield Promise.resolve(1);  // 不会执行
}

run(main).then(result => {
  console.log(result);  // "直接返回"
});

6.4 嵌套 Generator

function* inner() {
  let x = yield Promise.resolve(10);
  return x * 2;
}

function* outer() {
  // 需要 run 包装才能正确执行
  let result = yield run(inner);
  return result + 5;
}

run(outer).then(r => console.log(r));  // 25

6.5 并行执行

function* main() {
  // 错误:串行执行
  let a = yield fetchData(10, 100);
  let b = yield fetchData(20, 100);  // 等待 a 完成后才开始

  // 正确:并行执行
  let [c, d] = yield Promise.all([
    fetchData(10, 100),
    fetchData(20, 100)
  ]);
}

七、性能与优化

7.1 调用栈分析

每次 yield 都会创建新的 Promise 链:

handleResult → .then → handleNext → it.next → handleResult → ...

潜在问题: 深度递归可能导致调用栈增长

解决方案: 使用 setImmediateprocess.nextTick 断开调用链

// 优化版本
function handleResult(next) {
  if (next.done) {
    resolve(next.value);
  } else {
    Promise.resolve(next.value)
      .then(value => {
        // 断开调用链
        setImmediate(() => handleNext(value));
      })
      .catch(...);
  }
}

7.2 内存考量

  • 每个 yield 创建一个 Promise
  • 长时间运行的 Generator 可能积累内存
  • 建议:及时 return 结束 Generator

八、实际应用场景

8.1 数据库事务

function* transaction() {
  try {
    let conn = yield getConnection();
    yield conn.query("BEGIN");
    
    yield conn.query("INSERT INTO users ...");
    yield conn.query("UPDATE accounts ...");
    
    yield conn.query("COMMIT");
    return { success: true };
  } catch (err) {
    yield conn.query("ROLLBACK");
    throw err;
  }
}

8.2 重试机制

function* fetchWithRetry(url, maxRetries) {
  let lastError;
  for (let i = 0; i < maxRetries; i++) {
    try {
      return yield fetch(url);
    } catch (err) {
      lastError = err;
      yield delay(1000 * Math.pow(2, i));  // 指数退避
    }
  }
  throw lastError;
}

8.3 流程控制

function* workflow() {
  const user = yield getUser(userId);
  const orders = yield getOrders(user.id);
  const payments = yield getPayments(orders);
  
  return { user, orders, payments };
}

九、常见问题与陷阱

9.1 忘记 yield

function* main() {
  // 错误:忘记 yield,Promise 不会等待
  let a = fetchData(10, 100);  // a 是 Promise,不是 20
  
  // 正确
  let a = yield fetchData(10, 100);  // a 是 20
}

9.2 错误的异常捕获

function* main() {
  // 错误:try-catch 只能捕获 yield 的错误
  try {
    let a = yield fetchData(10, 100);
    a.nonExistentMethod();  // 这个错误不会被捕获!
  } catch (err) {
    console.log(err);
  }
}

9.3 this 绑定问题

const obj = {
  value: 42,
  *gen() {
    return this.value;  // 正确
  }
};

run(obj.gen);  // this 可能丢失

// 解决方案
run(obj.gen.bind(obj));
// 或
run(function*() { return obj.gen(); });

十、总结

核心要点

  1. 自动执行原理:通过递归调用 handleNexthandleResult 自动推进 Generator
  2. 值传递机制it.next(value) 将 Promise 结果传回 Generator
  3. 异常传播it.throw(err) 将错误抛回 Generator 内部处理
  4. Promise.resolve 妙用:统一处理 Promise 和普通值

设计精髓

Generator 提供暂停/恢复能力
    +
Promise 提供异步结果
    +
run 函数提供自动驱动
    =
同步风格的异步代码

学习价值

虽然 async/await 已经成为主流,但理解 run 函数的实现有助于:

  • 深入理解 JavaScript 异步机制
  • 理解 async/await 的底层原理
  • 在特殊场景下灵活运用
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐