Skip to content
生成器函数
概述
生成器函数通过 function* 声明。调用生成器函数不会立即执行函数体,而是返回一个生成器对象(Generator)。该对象实现了迭代器协议与可迭代协议,可以通过 next() 方法分步执行函数体,并在每个 yield 表达式处暂停,保留完整的执行上下文(局部变量、调用栈等),之后可从暂停点恢复执行。
生成器函数支持暂停/恢复执行、通过 yield 与外界双向通信,以及通过 yield* 将控制权委托给另一个生成器或可迭代对象。这些特性使生成器在惰性序列、异步流程控制、自定义迭代器等场景中有直接应用。
基本概念
迭代器协议与可迭代协议
迭代器协议要求对象实现 next() 方法,返回 {value, done} 结构。done 为 true 时表示遍历结束。
可迭代协议要求对象实现 [Symbol.iterator]() 方法,返回一个迭代器对象。
生成器对象同时满足两个协议:它自身就是迭代器(拥有 next() 方法),并且其 [Symbol.iterator]() 返回 this。因此生成器对象可以直接用于 for...of 循环、展开运算符和解构赋值。
基本用法
js
function* genFunc() {
yield 1;
yield 2;
return 3;
}
const genObj = genFunc(); // 创建生成器对象,函数体未执行
genObj.next(); // {value: 1, done: false}
genObj.next(); // {value: 2, done: false}
genObj.next(); // {value: 3, done: true}return 语句的值会作为 value 返回,同时 done 变为 true。此后再调用 next() 将得到 {value: undefined, done: true}。
工作原理
yield 暂停语义
yield 表达式使函数在执行过程中暂停,保留所有局部状态,下次调用 next() 时从暂停点继续执行,直到遇到下一个 yield 或函数结束。
以阶乘生成为例:
js
function* factorial(n) {
if (n === 0) {
return 1;
}
let result = 1;
for (let i = 1; i <= n; i++) {
result *= i;
yield result; // 产出当前 i 的阶乘值,并暂停
}
}调用 factorial(5) 创建生成器对象,首次 next() 执行到 yield result,产出一个阶乘值并暂停。再次 next() 从该位置之后继续执行,更新循环变量,再次到达 yield 暂停,直至循环结束。整个过程由调用方控制推进节奏,生成器本身相当于一个可控的状态机。
yield 双向通信
yield 不仅可以向外产出值,还是一个可以接收值的表达式:
js
function* dialogue() {
const answer1 = yield '你是谁?';
const answer2 = yield '你从哪里来?';
return `你好,${answer1},来自${answer2}`;
}
const gen = dialogue();
console.log(gen.next().value); // "你是谁?"
console.log(gen.next('小明').value); // "你从哪里来?"
console.log(gen.next('北京').value); // "你好,小明,来自北京"- 第一次
gen.next()启动生成器,执行到yield '你是谁?'暂停,并向外产出'你是谁?'。此时yield表达式的值尚未确定。 - 第二次
gen.next('小明')将'小明'作为上一个yield表达式的结果赋给answer1,同时继续执行到下一个yield '你从哪里来?'暂停,产出该字符串。 - 第三次
gen.next('北京')将'北京'赋给answer2,函数执行剩余代码并return最终字符串。
启动生成器的第一次 next() 传入的参数会被忽略,因为此时还没有 yield 表达式在等待接收值。生成器需要一个无参(或传参被忽略)的 next() 来启动,之后进入产出与接收的交替。
yield* 委托
yield* 将生成控制权委托给另一个生成器(或任意可迭代对象):
js
function* inner() {
yield 'A';
yield 'B';
}
function* outer() {
yield 'Start';
yield* inner(); // 委托 inner 的产出
yield 'End';
}
for (const val of outer()) {
console.log(val);
}
// 输出:
// Start
// A
// B
// End执行到 yield* inner() 时,outer 的生成器暂停,转而驱动 inner 的生成器。inner 的每一次 yield 都直接体现在 outer 生成的迭代序列中。inner 结束后,outer 从 yield* 之后继续执行。
这种委托机制可以将一个大的生成器拆分成多个小段,再通过 yield* 组合。每一步都保留了暂停能力。
API
Generator.prototype.next(value)
推进生成器执行。可选参数 value 作为上一个 yield 表达式的结果。返回 {value, done}。
- 首次
next()会启动执行,直到第一个yield或return。 - 生成器进入完成状态(
done: true)后继续调用next()返回{value: undefined, done: true}。
Generator.prototype.return(value)
提前终止生成器,使当前暂停的 yield 表达式结束,并直接返回给定的值。调用后生成器进入完成状态。
js
function* gen() {
yield 1;
yield 2;
yield 3;
}
const g = gen();
g.next(); // {value: 1, done: false}
g.return('提前结束'); // {value: '提前结束', done: true}
g.next(); // {value: undefined, done: true}return() 会跳过剩余所有 yield。但如果在 try...finally 中,finally 块仍会执行。
Generator.prototype.throw(exception)
向生成器内部抛出一个错误,该错误会在当前暂停的 yield 表达式处抛出。生成器内部可以捕获该异常:
js
function* gen() {
try {
yield 1;
} catch (e) {
console.log('内部捕获:', e.message);
}
yield 2;
}
const g = gen();
g.next(); // {value: 1, done: false}
g.throw(new Error('注入错误')); // 内部捕获: 注入错误
// {value: 2, done: false}如果生成器内部没有 try...catch,异常会穿透到调用方,生成器自身进入完成状态,后续 next() 调用均返回 {value: undefined, done: true}。
注意点
生成器对象不可重启:生成器对象一旦达到
done: true状态(不论是由return、throw未捕获异常还是自然结束),便无法重置或重新运行。需要重新调用生成器函数创建新实例。第一个
next()的参数会被忽略:启动生成器时还没有yield表达式等待接收值,因此传入任何值都不会产生效果。这一行为源于yield表达式的值由下一条next()的参数决定。异常处理:
throw()方法可以在外部观察生成器内部的异常处理行为。如果生成器内部捕获异常并继续,throw()会返回下一个yield产生的值;若异常未被捕获,生成器终止,错误向上传播。return不影响for...of:for...of循环会忽略return给出的值,只遍历yield产出的值。因为for...of在看到done: true时就会退出,不会读取最后的value。
常见应用
惰性序列
生成器本身是惰性的,只在调用 next() 时计算一个值,适合表达无限序列或按需生成的大数据集:
js
function* fibonacci() {
let [prev, curr] = [0, 1];
while (true) {
yield curr;
[prev, curr] = [curr, prev + curr];
}
}
const fib = fibonacci();
fib.next(); // {value: 1, done: false}
fib.next(); // {value: 1, done: false}
fib.next(); // {value: 2, done: false}
fib.next(); // {value: 3, done: false}调用方控制取值的节奏,不需要一次性计算所有值。
异步流程控制
yield 的暂停/恢复能力为异步流程控制提供了基础。早期的 co 库以及 redux-saga 都依赖生成器实现异步编排。
redux-saga 中的 Effect 本质上通过 yield 向中间层交出描述对象,中间层执行异步操作后再通过 next() 将结果传回生成器。这种方式把所有副作用都提到生成器之外,测试时可以直接对 yield 产出的对象进行断言,而无需执行真实副作用。
自定义迭代器
利用生成器可以简便地让任意对象支持迭代协议:
js
const range = {
from: 1,
to: 5,
*[Symbol.iterator]() {
for (let i = this.from; i <= this.to; i++) {
yield i;
}
}
};
console.log([...range]); // [1, 2, 3, 4, 5]对象内的 *[Symbol.iterator]() 方法是一个生成器函数,调用后返回的生成器对象自身就是迭代器,因此该对象可用于 for...of、展开运算等场景。
参考链接
- ECMAScript 语言规范:ECMAScript® 2026 Language Specification - Generator Objects
- MDN:function*
- MDN:迭代协议
- redux-saga 文档:Redux-Saga
- co 库:co
