概述
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() 方法,函数从上次暂停的位置继续执行,直到遇到下一个 yield 或 return。
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 的语法糖:
| Generator | async/await |
|---|---|
function* | async function |
yield | await |
| 需要手动执行器 | 内置执行器 |
| 返回 Iterator | 返回 Promise |
现在日常开发中,异步操作推荐直接使用 async/await。但理解 Generator 有助于:
- 理解 async/await 的底层原理
- 实现惰性求值和无限序列
- 需要细粒度控制迭代过程的场景
小结
Generator 函数的核心特征:
- 可暂停、可恢复的函数执行
- 通过
yield产出值,通过next(value)注入值 - 返回的对象遵循 Iterator 协议,可用于
for...of yield*实现 Generator 组合
实际价值:Generator 提供了一种协程机制,让 JavaScript 能够在单线程环境下模拟多任务协作。async/await 建立在这个基础之上,是 Generator 在异步场景下的专用语法。