Generator 函数是 ES6 引入的一种特殊函数类型,用于创建可暂停和恢复执行的函数。它通过 function*
语法定义,主要用来实现惰性计算和异步流程控制
函数运行的三种状态
核心特性
- 声明
Generator
函数:- 普通函数:
function* bar() {}
- 在对象中简写:
{ *foo() {} }
,相当于:{ foo: function*() {} }
- 普通函数:
yield
关键字- 在函数内部使用
yield
暂停执行并返回给外部(调用者)一个值 - 外部每次调用
next()
时恢复执行,直到下一个yield
或return
next(value)
可以给 Generator 函数内部传递值
- 在函数内部使用
- 返回迭代器对象
- 调用 Generator 函数不会直接执行,而是返回一个迭代器对象
function* myGenerator() {
yield 'Hello';
yield 'World';
}
const gen = myGenerator();
console.log(gen.next()); // { value: 'Hello', done: false }
console.log(gen.next()); // { value: 'World', done: false }
console.log(gen.next()); // { value: undefined, done: true }
// ---------------------------------------
// Generator 返回一个Iterator,所以支持 for...of 和扩展运算符
for (const val of myGenerator()) {
console.log(val); // 依次输出 Hello, World
}
[...gen2()] // ['Hello', 'World']
可交出执行权,可暂停可恢复
Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)
function* test() {
console.log('1111');
yield;
console.log('3333');
}
const g = test();
g.next(); // 1111
console.log('2222') // 2222
g.next() // 3333
Generator 函数不同于普通函数,是可以暂停执行的,所以函数名之前要加星号,以示区别
整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用 yield
语句注明
调用 Generator 函数,会返回一个内部指针迭代器 g
调用指针 g
的 next
方法,会移动内部指针(即执行异步任务的第一段),指向第一个遇到的 yield
语句为止
换言之,next
方法的作用是分阶段执行 Generator 函数。每次调用 next
方法,会返回一个对象,表示当前阶段的信息:
{
done: true/false,
value: xxx,
}
done
:一个布尔值,表示 Generator 函数是否执行完毕,即是否还有下一个阶段value
:yield
语句后面表达式的值,表示当前阶段的返回值
函数体内外的数据交换
next
方法返回值的 value
属性,是 Generator 函数向外输出数据:
function* gen() {
const a = yield 'First';
}
const g = gen();
const val = g.next(); // 触发执行直到下一个 yield 或 return,暂停
console.log(val); // { value: 'First', done: false }
next
方法还可以接受参数,这是向 Generator 函数体内输入数据:
function* gen() {
const a = yield 'First';
console.log(a); // 输出 'Input!'
}
const g = gen();
g.next(); // 执行到 yield,暂停
g.next('Input!'); // 将 'Input!' 赋值给 a
throw()
:错误处理
Generator 函数内部还可以部署错误处理代码,捕获函数体外抛出的错误
function* gen() {
while (true) {
try {
yield 1
} catch (e) {
console.log(e.message) // 第 6 行
}
}
}
const g = gen()
console.log(g.next()) // { value: 1, done: false }
g.throw(new Error('unknown error')) // 这时 Generator 函数内部捕获到错误
// 第 6 行执行,打印出 unknown error
console.log(g.next()) // { value: 1, done: false }
// Generator 函数内部捕获了错误,出错会结束本次 next
// 下一次 next 不受影响,继续正常执行
上面代码中,在 Generator 函数体外使用指针对象的 throw
方法抛出的错误,可以被函数体内的 try...catch
代码块捕获。这意味着,出错的代码与处理错误的代码,实现了时间和空间上的分离,这对于异步编程无疑是很重要的
return()
:强制结束迭代
不继续往下执行,直接结束 Generator 函数
function* gen() {
let val
val = yield [1, 2, 3]
console.log(val)
}
const g = gen()
// console.log(g.return()) // { value: undefined, done: true }
console.log(g.return(10)) // { value: 10, done: true }
// g.return() 传参只是影响 g.return() 返回值的 value
委托生成器 (yield*
)
在一个 Generator 中调用另一个 Generator:
function* gen1() {
yield 2;
}
function* gen2() {
yield 1;
yield* gen1(); // 委托执行
yield 3;
}
const g = gen2()
g.next() // 1
g.next() // 2
g.next() // 3
在一个 Generator 中调用 Iterator:
function* gen() {
let val
val = yield* [1, 2, 3]
console.log(val) // 第 4 行
}
const g = gen()
console.log(g.next()) // { value: 1, done: false }
console.log(g.next()) // { value: 2, done: false }
console.log(g.next()) // { value: 3, done: false }
console.log(g.next()) // { value: undefined, done: true }
// 调用这个 next,先执行第 4 行语句,打印 undefined
典型应用场景
按需生成序列
如斐波那契数列:
function* fibonacci() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
fib.next().value; // 0
fib.next().value; // 1
状态机实现
function* stateMachine() {
while (true) {
yield 'red';
yield 'green';
yield 'yellow';
}
}
状态机通常有多个状态,并且根据条件在不同状态间切换。可以模拟一个简单的交通灯状态机,它有红灯、绿灯、黄灯三个状态,并按照红灯 → 绿灯 → 黄灯 → 红灯…的顺序循环
另外,还可以让状态机在转换时返回当前状态以及可以停留的时间(比如红灯停 10 秒,绿灯停 5 秒,黄灯停 2 秒)
分步任务调度
例如当需要处理一个非常大的数据集时,一次处理全部可能会阻塞主线程,导致页面卡顿。使用 Generator 可以将其分解为小的可管理块:
// 创建处理大型数组的生成器函数
function* processLargeArray(array, chunkSize = 100) {
let processedCount = 0;
const totalItems = array.length;
while (processedCount < totalItems) {
// 计算当前处理的区间
const start = processedCount;
const end = Math.min(processedCount + chunkSize, totalItems);
// 处理当前数据块
const results = [];
for (let i = start; i < end; i++) {
// 模拟复杂的处理操作(实际应用中可以是任何计算)
const processedItem = heavyProcessing(array[i]);
results.push(processedItem);
}
// 更新进度并返回结果
processedCount += chunkSize;
const progress = (processedCount / totalItems * 100).toFixed(1);
// 暂停执行,允许事件循环处理其他任务
yield { progress, results, done: false };
}
return { progress: 100, results: null, done: true };
}
// 复杂的处理函数(模拟耗时操作)
function heavyProcessing(item) {
// 模拟CPU密集计算
let result = 0;
for (let i = 0; i < 100000; i++) {
result += Math.sqrt(item) * Math.random();
}
return result;
}
调度器实现:创建一个调度函数,使用 setTimeout
来控制执行节奏:
// 任务调度器
function scheduleTask(generator, onProgress, onComplete) {
const worker = generator;
let resume = true; // 控制是否继续执行
function nextStep() {
if (!resume) return;
try {
// 开始计时
const startTime = performance.now();
// 处理一个数据块
const { value, done } = worker.next();
// 报告进度
if (value && onProgress) {
onProgress(value);
}
// 检查是否完成
if (done && onComplete) {
onComplete();
return;
}
// 计算实际耗时
const elapsed = performance.now() - startTime;
// 动态调整延时:
// 如果执行耗时超过50ms,下次立即执行保持进度
// 如果执行很快(<30ms),增加短暂延时让出主线程
const delay = elapsed > 50 ? 0 : Math.max(30 - elapsed, 1);
// 调度下一次执行
setTimeout(nextStep, delay);
} catch (e) {
console.error("任务执行出错:", e);
resume = false;
}
}
// 开始调度
nextStep();
// 提供暂停方法
return {
pause: () => { resume = false; }
};
}
使用示例:
// 创建一个包含10,000条数据的大数组
const bigData = Array.from({ length: 10000 }, (_, i) => i + 1);
// 创建生成器
const dataProcessor = processLargeArray(bigData, 250);
// 开始调度处理
const controller = scheduleTask(
dataProcessor,
// 进度更新回调
({ progress }) => {
console.log(`处理进度: ${progress}%`);
// 实际项目中可以更新进度条或UI显示
document.getElementById('progress').textContent = `${progress}%`;
},
// 完成回调
() => {
console.log("所有数据处理完成!");
document.getElementById('result').textContent = "处理完成!";
}
);
// 添加暂停按钮功能(可选)
document.getElementById('pauseBtn').addEventListener('click', () => {
controller.pause();
console.log("处理已暂停");
});
注意事项
- 箭头函数不能用作 Generator
- 首次
next()
传参无效(参数会被忽略) - 适合有限状态迭代,无限迭代需手动终止