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));
总结
-
Generator 是什么:一个可以通过
yield暂停,通过next()恢复并传值的状态机。 -
转变关系:
-
async function====>function*+ 自动执行器 -
await promise====>yield promise
-
-
原理:
- 代码执行到
yield,暂停,返回 Promise 给执行器。 - 执行器等待 Promise
resolve。 - 执行器拿到结果,调用
next(结果),把数据还给函数内部,代码继续运行到下一个yield。
- 代码执行到
在日常开发中,直接使用 Generator 的场景已经很少了(主要被 async/await 取代),但在以下场景依然很有用:
- Redux-Saga:React 中处理复杂副作用的库,完全基于 Generator。
- 控制流:需要精细控制“暂停/取消”任务的时候。
- 迭代器:自定义复杂的遍历规则(例如遍历二叉树)。