Generator 函数​​是 ES6 引入的一种特殊函数类型,用于创建​​可暂停和恢复执行的函数​​。它通过 function* 语法定义,主要用来实现​​惰性计算​​和​​异步流程控制​

函数运行的三种状态

核心特性

  1. 声明 Generator 函数:
    • 普通函数:function* bar() {}
    • 在对象中简写:{ *foo() {} },相当于:{ foo: function*() {} }
  2. yield 关键字​​
    • 在函数内部使用 yield 暂停执行并返回给外部(调用者)一个值
    • 外部每次调用 next() 时恢复执行,直到下一个 yield 或 return
    • next(value) 可以给 Generator 函数内部传递值
  3. ​​返回迭代器对象​​
    • 调用 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

调用指针 gnext 方法,会移动内部指针(即执行异步任务的第一段),指向第一个遇到的 yield 语句为止

换言之,next 方法的作用是分阶段执行 Generator 函数。每次调用 next 方法,会返回一个对象,表示当前阶段的信息:

{
    done: true/false,
    value: xxx,
}
  • done:一个布尔值,表示 Generator 函数是否执行完毕,即是否还有下一个阶段
  • valueyield 语句后面表达式的值,表示当前阶段的返回值

函数体内外的数据交换

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() 传参无效​​(参数会被忽略)
  • ​​适合有限状态迭代​​,无限迭代需手动终止