人人都会AI编程

Generator 函数执行机制与异步应用

更新时间:2026-07-11

Generator 是 ES6 引入的一种特殊函数,它最大的特点是可以暂停执行,然后恢复执行。普通函数被调用后会一口气从头跑到尾,而 Generator 函数可以在中间某个点“刹车”,把控制权交还给调用者,等调用者“踩油门”后再继续往下跑。这一特性为异步编程、惰性计算、自定义迭代器等都提供了全新的可能。

基础语法与执行机制

Generator 函数的定义形式是 function*,内部使用 yield 关键字声明暂停点。

function* myGenerator() {
  console.log('第一步');
  const a = yield 1;
  console.log('第二步,收到的值是:', a);
  const b = yield 2;
  console.log('第三步,收到的值是:', b);
  return '结束';
}

调用 myGenerator() 并不会立即执行函数体,而是返回一个 迭代器对象(Generator 对象)。这个迭代器实现了迭代协议,拥有 next() 方法。每次调用 next(),函数从当前暂停点执行到下一个 yieldreturn,然后再次暂停。

const gen = myGenerator();

gen.next();     // 输出:第一步,返回 { value: 1, done: false }
gen.next(100);  // 输出:第二步,收到的值是: 100,返回 { value: 2, done: false }
gen.next(200);  // 输出:第三步,收到的值是: 200,返回 { value: '结束', done: true }
gen.next();     // 返回 { value: undefined, done: true }

关键机制

  • yield 后的值会被包装成 { value, done } 返回给调用者。
  • 调用者可以通过 next(参数) 向 Generator 内部注入数据,这个参数将作为上一个 yield 表达式的返回值。上例中 const a = yield 1; 在第二次 next(100) 时,a 才会被赋值为 100,而不是 1。
  • return 语句会使 done 变为 true,值为返回值。
  • Generator 函数执行完毕后,再调用 next() 已经没有意义。

双向通信与执行控制

Generator 不仅可以向外输出数据,还能向内接收数据,实现函数外部和内部的双向通信。这让调用者能够根据外部条件动态干预 Generator 内部的执行逻辑,如同在函数内外架起了一条双向管道。

function* talk() {
  const name = yield '你的名字是?';
  const age = yield `你好,${name},你的年龄是?`;
  yield `${name},你 ${age} 岁,欢迎!`;
}

const chat = talk();
chat.next();               // 启动对话
const resp1 = chat.next('Alice');  // 回答名字
chat.next(25);             // 回答年龄

错误处理

Generator 还提供了 throw() 方法,可以向暂停处抛入一个错误。错误可以在 Generator 内部用 try...catch 捕获。

function* errorGen() {
  try {
    yield 1;
  } catch (e) {
    console.log('内部捕获:', e);
  }
  yield 2;
}
const gen = errorGen();
gen.next();
gen.throw(new Error('出错了')); // 内部捕获
gen.next(); // 继续执行

如果内部没有捕获,错误会冒泡到调用处,Generator 也会被终止。

异步应用:配合 Promise 实现同步写法

Generator 的最惊艳应用在于处理异步流程。通过 yield 暂停,结合 Promise,可以将异步代码写得像同步一样,解决回调地狱。

核心思想:写一个自动执行的运行器,接收 Generator 函数,每次调用 next() 获取到 { value },如果是 Promise 就等待它完成,然后把结果传回 Generator 内部;如果不是 Promise,直接传回。如此循环直到完成。

一个简化的自动执行器(co 库的核心原理):

function run(genFn) {
  const gen = genFn();

  function handle(result) {
    if (result.done) return Promise.resolve(result.value);
    // 确保 value 是 Promise(也可处理普通值)
    return Promise.resolve(result.value).then(
      data => handle(gen.next(data)),
      err => handle(gen.throw(err))
    );
  }

  try {
    return handle(gen.next());
  } catch (err) {
    return Promise.reject(err);
  }
}

使用示例:

function* fetchUser() {
  const user = yield fetch('/api/user').then(res => res.json());
  const posts = yield fetch(`/api/posts/${user.id}`).then(res => res.json());
  return { user, posts };
}

run(fetchUser).then(result => {
  console.log('最终结果:', result);
});

这种用同步风格写异步操作的写法,虽然现在大多被 async/await 取代(async/await 本质上是 Generator + 自动执行器的语法糖),但理解 Generator 的异步应用,能帮助你洞悉 async/await 的底层实现。

其他实用场景

  • 惰性迭代器:Generator 可以按需生成序列,不一次性占用大量内存,比如生成无限序列、斐波那契数列。
  • 状态机:通过 yield 驱动状态转移。
  • 自定义遍历接口:给普通对象添加 Symbol.iterator 方法,返回一个 Generator,让 for...of 可以遍历该对象。

注意事项与总结

  • Generator 一旦迭代器完成(done: true),就不能再调用 next(),否则返回 { value: undefined, done: true }
  • yield* 可以委托给另一个 Generator 或可迭代对象,递归生成。
  • 在现代开发中,除非你需要精细控制异步流程或实现复杂的惰性迭代器,否则直接使用 async/await 会更简洁。但理解 Generator 是理解异步模型和语言演进的重要一环。