Skip to content
imshengli blog
Go back

ES6 Generator:yield、惰性求值与异步控制

Generator 函数的执行机制、yield 值传递、Iterator 协议,以及异步控制和惰性求值的实际应用。

· 8 min

概述

Generator 函数是 ES6 提供的一种异步编程解决方案。与普通函数不同,Generator 函数可以暂停执行和恢复执行,这使得它能够在函数执行过程中交出控制权。

从语法上看,Generator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象(Iterator),通过这个对象可以依次访问函数内部的每个状态。

基本语法

Generator 函数使用 function* 声明,函数体内使用 yield 表达式定义暂停点:

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

const hw = helloWorldGenerator();

hw.next(); // { value: 'hello', done: false }
hw.next(); // { value: 'world', done: false }
hw.next(); // { value: 'ending', done: true }
hw.next(); // { value: undefined, done: true }

调用 Generator 函数不会立即执行,而是返回一个遍历器对象。每次调用 next() 方法,函数从上次暂停的位置继续执行,直到遇到下一个 yieldreturn

yield 暂停与 next() 恢复

执行机制

Generator 函数的执行是”走走停停”的:

function* counter() {
  console.log('开始执行');
  yield 1;
  console.log('恢复执行');
  yield 2;
  console.log('再次恢复');
  return 3;
}

const gen = counter();

gen.next();
// 输出: '开始执行'
// 返回: { value: 1, done: false }

gen.next();
// 输出: '恢复执行'
// 返回: { value: 2, done: false }

gen.next();
// 输出: '再次恢复'
// 返回: { value: 3, done: true }

yield 的惰性求值

yield 后面的表达式只在 next() 调用时才会求值:

function* lazy() {
  yield expensiveComputation(); // 只有调用 next() 时才执行计算
  yield anotherComputation();
}

next() 的参数:值传递

next(value) 可以向 Generator 函数内部传值,这个值会作为上一个 yield 表达式的返回值:

function* calculate() {
  const x = yield '请输入 x';
  const y = yield '请输入 y';
  return x + y;
}

const gen = calculate();

gen.next();      // { value: '请输入 x', done: false }
gen.next(10);    // x = 10, { value: '请输入 y', done: false }
gen.next(20);    // y = 20, { value: 30, done: true }

注意:第一次调用 next() 时传参无效,因为此时还没有上一个 yield 表达式来接收值。

更实际的例子:

function* accumulator() {
  let total = 0;
  while (true) {
    const value = yield total;
    total += value;
  }
}

const acc = accumulator();
acc.next();    // { value: 0, done: false }  启动
acc.next(5);   // { value: 5, done: false }  total = 0 + 5
acc.next(10);  // { value: 15, done: false } total = 5 + 10
acc.next(3);   // { value: 18, done: false } total = 15 + 3

Generator 与 Iterator

Generator 函数执行后返回的对象同时实现了 Iterator 协议和 Iterable 协议,因此可以直接用于 for...of 循环:

for…of 遍历

function* fibonacci() {
  let prev = 0;
  let curr = 1;
  while (true) {
    yield curr;
    [prev, curr] = [curr, prev + curr];
  }
}

// 取前 10 个斐波那契数
for (const num of fibonacci()) {
  if (num > 100) break;
  console.log(num); // 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89
}

注意:for...of 会忽略 return 语句的值(done: true 时的 value 不会被遍历到)。

给对象添加 Iterator 接口

function* objectEntries(obj) {
  const keys = Object.keys(obj);
  for (const key of keys) {
    yield [key, obj[key]];
  }
}

const person = { name: 'Alice', age: 25, city: 'Beijing' };

for (const [key, value] of objectEntries(person)) {
  console.log(`${key}: ${value}`);
}

展开运算符与解构

Generator 返回的 Iterator 也支持展开运算符和解构:

function* range(start, end) {
  for (let i = start; i < end; i++) {
    yield i;
  }
}

const nums = [...range(1, 6)]; // [1, 2, 3, 4, 5]
const [first, second] = range(10, 20); // first=10, second=11

Generator 的控制方法

return()

提前终止 Generator:

function* gen() {
  yield 1;
  yield 2;
  yield 3;
}

const g = gen();
g.next();        // { value: 1, done: false }
g.return('end'); // { value: 'end', done: true }
g.next();        // { value: undefined, done: true }

throw()

向 Generator 内部抛出错误:

function* gen() {
  try {
    yield 1;
    yield 2;
  } catch (e) {
    console.log('内部捕获:', e);
  }
  yield 3;
}

const g = gen();
g.next();           // { value: 1, done: false }
g.throw('出错了');   // 内部捕获: 出错了, { value: 3, done: false }

实际应用场景

异步流程控制

在 async/await 出现之前,Generator 配合执行器是处理异步的主流方案:

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

  function step(result) {
    if (result.done) return result.value;
    return Promise.resolve(result.value).then(
      value => step(gen.next(value)),
      error => step(gen.throw(error))
    );
  }

  return step(gen.next());
}

// 使用
run(function* () {
  const user = yield fetch('/api/user').then(r => r.json());
  const posts = yield fetch(`/api/posts?userId=${user.id}`).then(r => r.json());
  console.log(posts);
});

这正是 async/await 的底层原理——自动执行 Generator 函数。

惰性求值

Generator 天然适合处理大数据集,按需计算避免内存浪费:

function* naturals() {
  let n = 1;
  while (true) {
    yield n++;
  }
}

function* filter(iterable, predicate) {
  for (const item of iterable) {
    if (predicate(item)) {
      yield item;
    }
  }
}

function* take(iterable, count) {
  let i = 0;
  for (const item of iterable) {
    if (i >= count) return;
    yield item;
    i++;
  }
}

// 获取前 5 个偶数,不会创建无限数组
const firstFiveEvens = [...take(filter(naturals(), n => n % 2 === 0), 5)];
console.log(firstFiveEvens); // [2, 4, 6, 8, 10]

无限序列

Generator 可以表示无限序列而不消耗内存:

// 无限 ID 生成器
function* idGenerator(prefix = '') {
  let id = 1;
  while (true) {
    yield `${prefix}${id++}`;
  }
}

const userIds = idGenerator('user_');
userIds.next().value; // 'user_1'
userIds.next().value; // 'user_2'
userIds.next().value; // 'user_3'

状态机

Generator 的暂停/恢复特性适合实现状态机:

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

const light = trafficLight();
light.next().value; // 'green'
light.next().value; // 'yellow'
light.next().value; // 'red'
light.next().value; // 'green' (循环)

yield* 委托

yield* 可以在一个 Generator 函数中执行另一个 Generator:

function* inner() {
  yield 'a';
  yield 'b';
}

function* outer() {
  yield 1;
  yield* inner(); // 委托给 inner
  yield 2;
}

const gen = outer();
gen.next(); // { value: 1, done: false }
gen.next(); // { value: 'a', done: false }
gen.next(); // { value: 'b', done: false }
gen.next(); // { value: 2, done: false }

yield* 后面可以跟任何可迭代对象:

function* concat(...iterables) {
  for (const iterable of iterables) {
    yield* iterable;
  }
}

const result = [...concat([1, 2], 'ab', new Set([3, 4]))];
// [1, 2, 'a', 'b', 3, 4]

与 async/await 的关系

async/await 可以看作 Generator 的语法糖:

Generatorasync/await
function*async function
yieldawait
需要手动执行器内置执行器
返回 Iterator返回 Promise

现在日常开发中,异步操作推荐直接使用 async/await。但理解 Generator 有助于:

小结

Generator 函数的核心特征:

  1. 可暂停、可恢复的函数执行
  2. 通过 yield 产出值,通过 next(value) 注入值
  3. 返回的对象遵循 Iterator 协议,可用于 for...of
  4. yield* 实现 Generator 组合

实际价值:Generator 提供了一种协程机制,让 JavaScript 能够在单线程环境下模拟多任务协作。async/await 建立在这个基础之上,是 Generator 在异步场景下的专用语法。


Share this post on:

Previous Post
ES6 Async/Await:异步编程的终极方案
Next Post
HTML5 Blob:二进制操作、文件下载与分片上传