JavaScript生成器(Generator)详解:从基础使用到实际场景

摘要:Generator是ES6引入的一种特殊函数。简单来说,它能暂停执行,也能继续执行,还可以分批次返回结果。普通函数一旦调用就会从头执行到尾,而Generator函数就像带了暂停键

Generator是ES6引入的一种特殊函数。简单来说,它能暂停执行,也能继续执行,还可以分批次返回结果。

普通函数一旦调用就会从头执行到尾,而Generator函数就像带了暂停键,可以随时停止,随时继续。


Generator的基本用法

定义Generator函数

在function后面加一个星号,函数内部使用yield关键字来暂停执行:

function* numberGenerator() {
  yield 1;  // 第一次暂停,返回1
  yield 2;  // 第二次暂停,返回2
  return 3; // 函数结束,返回3
}

使用Generator函数

// 创建生成器对象
const gen = numberGenerator();

// 每次调用next()方法,函数就执行到下一个yield或return
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: true }
console.log(gen.next()); // { value: undefined, done: true }

可以这样理解:普通函数像自动售货机,一次给出所有结果;Generator函数像抓娃娃机,每次操作只出一个结果。


Generator的工作原理

Generator函数调用时不会立即执行,而是返回一个生成器对象。这个对象记住函数的执行位置。

每次调用next()方法:

  • 从上次暂停的位置继续执行

  • 遇到yield就暂停,返回yield后面的值

  • 遇到return就结束,done变为true


Generator的高级特性

双向通信

可以向Generator函数内部传递数据:

function* chatGenerator() {
  const name = yield "请问你叫什么名字?";
  yield `你好,${name}!`;
}

const chat = chatGenerator();
console.log(chat.next().value);  // "请问你叫什么名字?"
console.log(chat.next("张三").value); // "你好,张三!"

错误处理

可以从外部向Generator函数抛出错误:

function* safeGenerator() {
  try {
    yield "第一步";
    yield "第二步";
  } catch (error) {
    yield `出错:${error.message}`;
  }
}

const gen = safeGenerator();
console.log(gen.next().value); // "第一步"
console.log(gen.throw(new Error("网络错误")).value); // "出错:网络错误"

委托其他Generator

使用yield*可以委托其他Generator函数执行:

function* gen1() {
  yield 1;
  yield 2;
}

function* gen2() {
  yield* gen1(); // 执行gen1
  yield 3;
}

const result = [...gen2()]; // [1, 2, 3]


Generator的实际应用场景

简化异步编程

在async/await出现之前,Generator常用于处理异步操作:

function* fetchUserData() {
  const user = yield fetch('/api/user');
  const posts = yield fetch(`/api/posts?userId=${user.id}`);
  return posts;
}

// 需要配合执行器使用
function run(generator) {
  const gen = generator();
  
  function handle(result) {
    if (result.done) return result.value;
    
    return result.value.then(data => {
      return handle(gen.next(data));
    });
  }
  
  return handle(gen.next());
}

run(fetchUserData).then(posts => {
  console.log('用户文章:', posts);
});

生成无限序列

Generator很适合生成无限序列,因为它是按需生成的:

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

const fib = fibonacci();

// 只取前10个斐波那契数
for (let i = 0; i < 10; i++) {
  console.log(fib.next().value);
}

处理大数据集

当需要处理大量数据时,可以分批处理:

function* batchProcessor(data, batchSize = 100) {
  for (let i = 0; i < data.length; i += batchSize) {
    const batch = data.slice(i, i + batchSize);
    yield batch; // 每次返回一批数据
  }
}

const bigData = new Array(10000).fill(0).map((_, i) => i);

for (const batch of batchProcessor(bigData, 1000)) {
  console.log(`处理批次,大小: ${batch.length}`);
  // 处理这一批数据
}

状态管理

Generator可以用于管理状态流转:

function* orderStateMachine() {
  yield "待支付";
  yield "已支付"; 
  yield "已发货";
  yield "已完成";
}

const order = orderStateMachine();

// 模拟订单状态变化
console.log(order.next().value); // "待支付"
console.log(order.next().value); // "已支付"
console.log(order.next().value); // "已发货"

遍历复杂数据结构

Generator让遍历复杂结构变得简单:

function* traverseTree(node) {
  if (!node) return;
  
  yield node.value;
  
  if (node.children) {
    for (const child of node.children) {
      yield* traverseTree(child);
    }
  }
}

const tree = {
  value: '根节点',
  children: [
    {
      value: '子节点1',
      children: [
        { value: '孙节点1' }
      ]
    },
    { value: '子节点2' }
  ]
};

for (const value of traverseTree(tree)) {
  console.log(value); // 依次输出所有节点值
}


Generator与async/await的关系

async/await实际上是Generator的语法糖,但它们各有用途:

特性Generatorasync/await
语法function* + yieldasync + await
执行需要手动调用next()自动执行
用途多种场景主要处理异步
错误处理使用throw()方法使用try/catch

为什么还要学习Generator?

  1. 理解底层原理:明白Generator如何工作,才能更好理解async/await

  2. 处理复杂遍历:对于无限序列、树结构遍历,Generator更合适

  3. 精确控制执行:在需要手动控制执行节奏的场景中很有用


实用技巧

创建可取消的任务

function* cancellableTask() {
  let isCancelled = false;
  
  try {
    yield "任务开始";
    
    // 模拟长时间运行的任务
    for (let i = 0; i < 100; i++) {
      if (isCancelled) {
        yield "任务已取消";
        return;
      }
      yield `进度: ${i}%`;
    }
    
    yield "任务完成";
  } catch (error) {
    yield `任务出错: ${error.message}`;
  }
  
  return {
    cancel: () => { isCancelled = true; }
  };
}

实现简单的状态机

function* trafficLight() {
  while (true) {
    yield "绿灯 - 通行";
    yield "黄灯 - 注意"; 
    yield "红灯 - 停止";
  }
}

const light = trafficLight();

setInterval(() => {
  console.log(light.next().value);
}, 3000);


总结

Generator为JavaScript带来了可控的执行流程,让函数可以在执行过程中暂停和继续。

虽然async/await在异步编程中更常用,但Generator在以下场景中仍然不可替代:

  • 生成无限序列

  • 遍历复杂数据结构

  • 分批处理大数据

  • 实现状态机

  • 需要精确控制执行流程的场景

学习Generator不仅能解决实际问题,还能加深对JavaScript执行机制的理解。它是从"会用JavaScript"到"懂JavaScript"的重要一步。

本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://shenqiku.cn/article/FLY_13206