生成器函数:Generator 函数

44 阅读3分钟

JavaScript 中的 Generator 函数(生成器函数)是一种特殊类型的函数,它允许你定义一个可暂停和恢复执行的函数。这是 ES6(ECMAScript 2015)引入的重要特性之一,主要用于实现惰性求值、异步编程、迭代器模式等场景。


一、基本语法

定义方式

使用 function* 关键字定义:

js
编辑
function* myGenerator() {
  yield 1;
  yield 2;
  return 3;
}
  • function*:声明一个生成器函数。
  • yield:暂停函数执行,并返回一个值;下次调用 .next() 时从该位置继续执行。
  • return:结束生成器,返回最终值(之后再调用 .next() 返回 { done: true, value: undefined })。

二、调用与执行

生成器函数不会像普通函数那样立即执行,而是返回一个 迭代器对象(Iterator)

js
编辑
const gen = myGenerator(); // 不执行函数体,只返回迭代器
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: true }
console.log(gen.next()); // { value: undefined, done: true }

每次调用 .next()

  • 恢复执行,直到遇到下一个 yield 或 return
  • 返回一个对象 { value: ..., done: ... }

三、yield 表达式的双向通信

yield 不仅可以“产出”值,还可以“接收”外部传入的值:

js
编辑
function* echo() {
  const input1 = yield '请输入第一个值';
  console.log('收到:', input1);
  const input2 = yield '请输入第二个值';
  console.log('收到:', input2);
}

const e = echo();
console.log(e.next());           // { value: "请输入第一个值", done: false }
console.log(e.next('Hello'));    // 收到: Hello → { value: "请输入第二个值", done: false }
console.log(e.next('World'));    // 收到: World → { value: undefined, done: true }

注意:第一次调用 .next() 时传参是无效的(因为还没遇到第一个 yield)。


四、与迭代协议(Iterable Protocol)结合

生成器函数天然实现了 可迭代协议([Symbol.iterator]) ,因此可以直接用于 for...of 循环:

js
编辑
function* countUpTo(max) {
  for (let i = 1; i <= max; i++) {
    yield i;
  }
}

for (const num of countUpTo(3)) {
  console.log(num); // 1, 2, 3
}

注意:for...of 会自动忽略 return 的值(因为它在 done: true 之后),只遍历 yield 的值。


五、错误处理

可以在生成器内部或外部抛出/捕获错误:

1. 外部向生成器抛出错误:

js
编辑
function* gen() {
  try {
    yield 1;
    yield 2;
  } catch (e) {
    console.log('捕获错误:', e.message);
  }
}

const g = gen();
console.log(g.next());        // { value: 1, done: false }
g.throw(new Error('出错了!')); // 捕获错误: 出错了! → { value: undefined, done: true }

2. 生成器内部抛出错误:

js
编辑
function* badGen() {
  yield 1;
  throw new Error('内部错误');
}

const bg = badGen();
bg.next(); // { value: 1, done: false }
bg.next(); // 抛出异常!需用 try/catch 包裹

六、实际应用场景

1. 惰性序列生成

js
编辑
function* fibonacci() {
  let a = 0, b = 1;
  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}

const fib = fibonacci();
console.log(fib.next().value); // 0
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
// 可无限生成,但只在需要时计算

2. 简化异步流程控制(配合 Promise)

虽然现代 JS 更多用 async/await,但早期常用生成器 + co 库实现异步:

js
编辑
function* asyncTask() {
  const data = yield fetch('/api/data').then(r => r.json());
  console.log(data);
}

// 需要一个运行器(如 co)来自动处理 .next() 和 Promise

3. 状态机

js
编辑
function* trafficLight() {
  while (true) {
    yield 'green';
    yield 'yellow';
    yield 'red';
  }
}

const light = trafficLight();
console.log(light.next().value); // green
console.log(light.next().value); // yellow
console.log(light.next().value); // red

七、与 async/await 的关系

  • 相似点:都能暂停/恢复执行。

  • 不同点

    • async/await 是基于 Promise 的语法糖,专为异步设计。
    • Generator 更通用,可用于迭代、状态机、协程等。
    • await 自动等待 Promise,而 yield 需要手动处理(除非配合库如 co)。

八、注意事项

  • 生成器函数不能作为构造函数(不能用 new 调用)。
  • 一旦 done: true,后续 .next() 不会再执行函数体。
  • 生成器是一次性的:不能重置,若需重新开始,必须创建新实例。

总结

特性说明
关键字function*
控制流yield 暂停,.next() 恢复
返回值迭代器对象(Iterator)
通信双向:yield 传出,.next(val) 传入
协议实现了 Iterable 接口
应用惰性计算、异步控制、状态机、自定义迭代

Generator 是 JavaScript 中非常强大且灵活的工具,理解它有助于深入掌握迭代器、异步编程和函数式编程思想。

如果你感兴趣,我也可以展示如何用 Generator 实现一个简单的协程调度器!