普通视图

发现新文章,点击刷新页面。
今天 — 2026年1月21日首页

generator的学习

2026年1月21日 15:02

JS 中的 Generator(生成器) 是一个非常强大但略显晦涩的概念。它是理解现代 JS 异步编程(特别是 async/await 原理)的基石。

简单来说,async/await 就是 Generator 加上一个自动执行器(Auto Runner)的语法糖。

下面我将分三个阶段带你学习:从基本用法,到底层原理,最后手写一个 async/await 的实现


第一阶段:Generator 怎么用?(基础语法)

普通函数一旦执行,就会从头跑到尾。而 Generator 函数是可以“中途暂停”和“恢复执行”的函数

1. 核心关键词

  • function*:声明一个生成器函数。
  • yield:暂停点(产出值)。
  • .next():遥控器(继续执行)。

2. 最简单的例子

function* helloWorldGenerator() {
  console.log("1. 开始执行");
  yield 'hello'; // 暂停在这里,并把 'hello' 扔出去
  
  console.log("2. 恢复执行");
  yield 'world'; // 再次暂停,把 'world' 扔出去
  
  console.log("3. 结束");
  return 'ending'; // 最终结束
}

// 1. 调用 Generator 不会立即执行代码,而是返回一个“遍历器对象”(指针)
const gen = helloWorldGenerator();

// 2. 第一次调用 next(),代码运行到第一个 yield 处停止
const result1 = gen.next(); 
console.log(result1); // { value: 'hello', done: false }

// 3. 第二次调用 next(),代码从上次停止的地方继续,直到下一个 yield
const result2 = gen.next();
console.log(result2); // { value: 'world', done: false }

// 4. 第三次调用 next(),代码运行到结束或 return
const result3 = gen.next();
console.log(result3); // { value: 'ending', done: true }

3. 双向数据交换(重要)

yield 不仅能输出数据,还能通过 next(参数) 接收数据。这是 async/await 能实现的关键。

function* calculate() {
  // 注意:yield 表达式本身没有返回值,val1 的值取决于下一次 next() 传进来的参数
  const val1 = yield 1; 
  console.log(`接收到了: ${val1}`);
  
  const val2 = yield 2;
  console.log(`接收到了: ${val2}`);
  
  return val1 + val2;
}

const gen = calculate();

// 第1步:启动,运行到 yield 1。此时 val1 还没赋值。
console.log(gen.next().value); // 输出: 1

// 第2步:传入 10。这个 10 会被赋值给上一个 yield 的返回值(即 val1)
console.log(gen.next(10).value); // 控制台: "接收到了: 10", 输出: 2

// 第3步:传入 20。这个 20 会被赋值给 val2
console.log(gen.next(20)); // 控制台: "接收到了: 20", 输出: { value: 30, done: true }

第二阶段:为什么说 async/await 是 Generator 变的?

让我们来看一个异步的场景。

1. 目标:顺序读取两个文件(模拟网络请求)

如果用 Generator 写,大概长这样:

function* myTask() {
  // 看起来是不是非常像 async/await ?
  const data1 = yield fetch('/api/user'); // 假设 fetch 返回 Promise
  console.log(data1);
  
  const data2 = yield fetch('/api/posts');
  console.log(data2);
}

2. 手动执行(痛点)

上面的代码虽然长得像同步代码,但它不会自己动。我们需要手动处理 Promise:

const gen = myTask();

const p1 = gen.next().value; // 拿到第一个 Promise

p1.then(data1 => {
  // 拿到数据后,通过 next(data1) 传回给 generator,并继续执行
  const p2 = gen.next(data1).value; 
  
  p2.then(data2 => {
    // 拿到第二个数据,传回去
    gen.next(data2);
  });
});

痛点:如果流程很长,这种嵌套(Callback Hell)依然存在,只是换了个地方。我们需要一个能自动执行 next() 的东西。


第三阶段:手写 async/await 实现(自动执行器)

async/await 的本质就是:Generator 函数 + 自动执行器(Auto Runner)

  • async 函数 = Generator 函数
  • await = yield
  • 自带的引擎 = 下面我们要写的 run 函数

我们来写一个函数 run,它接受一个 Generator,然后自动帮你调用 .then().next()

1. 核心代码(背下来就是原理)

function run(generatorFunc) {
  // 1. 初始化生成器
  const gen = generatorFunc();

  // 2. 定义递归函数,用来处理每一步
  function step(nextF) {
    let next;
    try {
      next = nextF(); // 执行 gen.next()
    } catch(e) {
      return Promise.reject(e);
    }

    // 3. 如果 generator 结束了,直接返回最终结果
    if(next.done) {
      return Promise.resolve(next.value);
    }

    // 4. 如果没结束,next.value 通常是一个 Promise
    // 我们用 Promise.resolve 包裹它(以防它不是 Promise)
    Promise.resolve(next.value).then(
      // Promise 成功:把结果 v 传回给 generator,继续下一步
      (v) => step(() => gen.next(v)), 
      
      // Promise 失败:把错误 e 抛回给 generator
      (e) => step(() => gen.throw(e))
    );
  }

  // 5. 启动递归
  return step(() => gen.next());
}

2. 验证效果

现在我们可以像写 async/await 一样写 Generator 了:

// 模拟一个异步请求
const getData = (n) => new Promise(resolve => setTimeout(() => resolve(n * 2), 1000));

// 这里的 function* 相当于 async function
function* main() {
  console.log("开始");
  
  // yield 相当于 await
  const r1 = yield getData(10); 
  console.log(`r1: ${r1}`); // 1秒后输出 20
  
  const r2 = yield getData(20);
  console.log(`r2: ${r2}`); // 再过1秒后输出 40
  
  return "完成";
}

// 运行它!
run(main).then(result => console.log(result)); 

总结

  1. Generator 是什么:一个可以通过 yield 暂停,通过 next() 恢复并传值的状态机。
  2. 转变关系
    • async function ====> function* + 自动执行器
    • await promise ====> yield promise
  3. 原理
    • 代码执行到 yield,暂停,返回 Promise 给执行器。
    • 执行器等待 Promise resolve
    • 执行器拿到结果,调用 next(结果),把数据还给函数内部,代码继续运行到下一个 yield

在日常开发中,直接使用 Generator 的场景已经很少了(主要被 async/await 取代),但在以下场景依然很有用:

  • Redux-Saga:React 中处理复杂副作用的库,完全基于 Generator。
  • 控制流:需要精细控制“暂停/取消”任务的时候。
  • 迭代器:自定义复杂的遍历规则(例如遍历二叉树)。
❌
❌