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

更新日期: 2025-11-20 阅读: 46 标签: 场景

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://fly63.com/article/detial/13206

async/await 异步应用的常用场景

async/await 语法用看起来像写同步代码的方式来优雅地处理异步操作,但是我们也要明白一点,异步操作本来带有复杂性,像写同步代码的方式并不能降低本质上的复杂性,所以在处理上我们要更加谨慎, 稍有不慎就可能写出不是预期执行的代码,从而影响执行效率

vue中$refs, $emit, $on, $once, $off的使用

$refs的使用场景:父组件调用子组件的方法,可以传递数据。$emit的使用:子组件调用父组件的方法并传递数据。$on的使用场景:兄弟组件之间相互传递数据

php中-> 、=>、::、$this->使用方法与场景

->用来引用一个类的属性(变量)、方法(函数);=>是用来定义数组用的;::用来直接调用类中的属性或方法,没有实例化;$this->表示实例化后调用具体对象

react之Fragments使用方法及使用场景

React 中的一个常见模式是一个组件返回多个元素。Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。还有一种新的短语法可用于声明它们。

html5不常用标签应用场景

作为一个前端开发,在浏览别人家的页面时总是会习惯性的查看他们页面的源码,发现大多数网站的页面,包括我自己写的页面中用到的最多的布局元素无外乎就是div、p、span、ul、dl、ol、li、dt、dd、strong、b

Js中的this原理及6种常见使用场景

this是JavaScript的一个关键字,函数调用时才会出现;因为函数是在一定的环境中运行的,调用函数时肯定需要知道是[谁调用的]?就用到了this进行指向;this 既不指向函数自身,也不指函数的词法作用域,而是调用函数时的对象!

HTML 元素标签语义化及使用场景

标签语义化就是让元素标签做适当的事情。例如 p 标签就是代表文本,button 标签代表按钮,nav 标签代表导航等等。其实标签语义化是给浏览器和搜索引擎看的。

JS中try-catch代码块的应用场景

try-catch属于同步代码块,因此无法捕获异步(重新开辟的线程,例如定时器,异步请求)代码中的异常,即能被try-catch捕获的异常,必须是在报错时候,线程的执行进入了try-catch代码块时,才能被捕获异常

React中useEffect的5个核心应用场景与避坑指南

在React函数式组件开发中,useEffect是处理副作用的基石。但许多开发者仅停留在基础用法,未能充分发挥其价值。本文将深入剖析5个高频应用场景,同时纠正常见误解,助你写出更健壮的组件。

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!