Generator 全面解析 + async/await 深度对比
一、Generator 核心概念
Generator 是 ES6 引入的可暂停函数,执行过程中可以多次进出,每次进出都会携带数据。
1.1 基本语法
javascript
// 声明:function*
function* myGenerator() {
// yield:暂停点
yield 1;
yield 2;
yield 3;
return 'end';
}
const gen = myGenerator(); // 调用不执行,返回迭代器对象
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: 'end', done: true }
1.2 核心特性
① 双向通信
javascript
function* twoWay() {
const name = yield '你叫什么名字?';
const age = yield `${name},你多大了?`;
return `${name}今年${age}岁`;
}
const gen = twoWay();
console.log(gen.next()); // { value: '你叫什么名字?', done: false }
console.log(gen.next('张三')); // { value: '张三,你多大了?', done: false }
console.log(gen.next(18)); // { value: '张三今年18岁', done: true }
② 惰性求值(按需计算)
javascript
function* infiniteNumbers() {
let i = 0;
while (true) { // 无限循环但不卡死
yield i++;
}
}
const numbers = infiniteNumbers();
console.log(numbers.next().value); // 0
console.log(numbers.next().value); // 1
console.log(numbers.next().value); // 2
// 需要多少取多少,不会预先计算所有值
③ 内存效率高
javascript
// 普通数组:一次性创建,占用大量内存
const bigArray = Array.from({ length: 1000000 }, (_, i) => i);
// Generator:边取边算,几乎不占内存
function* bigGenerator() {
for (let i = 0; i < 1000000; i++) {
yield i;
}
}
const gen = bigGenerator();
// 每次只计算一个值,内存占用恒定
1.3 Generator 的方法
javascript
function* demo() {
try {
yield 1;
yield 2;
yield 3;
} catch (e) {
console.log('捕获错误:', e.message);
}
}
const gen = demo();
// throw():向内部抛出错误
gen.next();
gen.throw(new Error('出错了')); // 捕获错误: 出错了
// return():提前终止
const gen2 = demo();
gen2.next();
gen2.return('提前结束'); // { value: '提前结束', done: true }
1.4 yield* 委托
javascript
function* gen1() {
yield 1;
yield 2;
}
function* gen2() {
yield 'a';
yield* gen1(); // 委托给 gen1
yield 'b';
}
console.log([...gen2()]); // ['a', 1, 2, 'b']
1.5 实现自定义迭代器
javascript
class ColorSet {
constructor(colors) {
this.colors = colors;
}
// 通过 Generator 实现迭代器协议
*[Symbol.iterator]() {
for (let i = 0; i < this.colors.length; i++) {
yield this.colors[i];
}
}
}
const colors = new ColorSet(['红', '绿', '蓝']);
for (let color of colors) {
console.log(color); // 红 绿 蓝
}
二、async/await 核心概念
async/await 是 ES2017 引入的异步编程语法糖,基于 Promise 实现。
javascript
// async 函数必定返回 Promise
async function fetchData() {
// await 等待 Promise 完成
const user = await fetch('/api/user').then(r => r.json());
const posts = await fetch(`/api/posts/${user.id}`).then(r => r.json());
return { user, posts };
}
// 调用方式
fetchData()
.then(data => console.log(data))
.catch(err => console.error(err));
三、深度对比
3.1 设计哲学对比
| 维度 | Generator | async/await |
|---|---|---|
| 设计目的 | 通用可暂停函数 | 简化 Promise 异步流程 |
| 暂停机制 | yield 任意值 | await 只能等待 Promise |
| 执行控制 | 外部手动控制 | 自动执行到底 |
| 返回值 | 迭代器对象 | Promise |
| 适用场景 | 迭代、状态机、协程 | 异步流程编排 |
3.2 代码对比示例
场景1:异步流程控制
javascript
// ========== Generator 方式 ==========
function* generatorFlow() {
const user = yield fetch('/api/user').then(r => r.json());
const posts = yield fetch(`/api/posts/${user.id}`).then(r => r.json());
return posts;
}
// 需要手动编写执行器
function run(generator) {
const gen = generator();
function handle(result) {
if (result.done) return Promise.resolve(result.value);
return Promise.resolve(result.value)
.then(data => handle(gen.next(data)))
.catch(err => handle(gen.throw(err)));
}
return handle(gen.next());
}
run(generatorFlow).then(posts => console.log(posts));
// ========== async/await 方式 ==========
async function asyncFlow() {
const user = await fetch('/api/user').then(r => r.json());
const posts = await fetch(`/api/posts/${user.id}`).then(r => r.json());
return posts;
}
asyncFlow().then(posts => console.log(posts));
// 简单直接,无需手动执行器
场景2:状态机实现
javascript
// ========== Generator 实现状态机 ==========
function* trafficLight() {
while (true) {
yield '红灯'; // 等待30秒
yield '绿灯'; // 等待30秒
yield '黄灯'; // 等待3秒
}
}
const light = trafficLight();
setInterval(() => {
console.log(light.next().value);
}, 30000); // 每30秒切换
// ========== async/await 实现状态机 ==========
async function asyncTrafficLight() {
while (true) {
console.log('红灯');
await sleep(30000);
console.log('绿灯');
await sleep(30000);
console.log('黄灯');
await sleep(3000);
}
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
asyncTrafficLight(); // 但无法从外部控制暂停
场景3:处理无限序列
javascript
// ========== Generator 处理无限序列 ==========
function* fibonacci() {
let [a, b] = [0, 1];
while (true) {
yield b;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
// 可以随时停止,不会无限循环
// ========== async/await 处理无限序列 ==========
async function asyncFibonacci() {
let [a, b] = [0, 1];
const results = [];
while (true) {
results.push(b);
[a, b] = [b, a + b];
if (results.length > 100) break; // 必须设置退出条件
await new Promise(r => setTimeout(r, 0)); // 避免阻塞
}
return results; // 一次性返回所有值
}
// 不适合处理无限序列,需要手动限制
场景4:错误处理
javascript
// ========== Generator 错误处理 ==========
function* genError() {
try {
yield 1;
yield 2;
} catch (err) {
console.log('内部捕获:', err.message);
yield '错误恢复';
}
yield 3;
}
const gen = genError();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.throw(new Error('出错了')));
// 内部捕获: 出错了
// { value: '错误恢复', done: false }
console.log(gen.next()); // { value: 3, done: false }
// ========== async/await 错误处理 ==========
async function asyncError() {
try {
await Promise.resolve(1);
await Promise.reject(new Error('出错了'));
} catch (err) {
console.log('捕获:', err.message);
return '错误恢复';
}
}
asyncError().then(console.log); // 捕获: 出错了
3.3 性能对比
javascript
// 测试:处理 10 万个数字
// Generator - 内存占用小
function* genSum() {
let sum = 0;
for (let i = 0; i < 100000; i++) {
sum += i;
yield sum;
}
}
// async/await - 需要收集结果
async function asyncSum() {
let sum = 0;
const results = [];
for (let i = 0; i < 100000; i++) {
sum += i;
results.push(sum);
if (i % 1000 === 0) await Promise.resolve(); // 避免长时间阻塞
}
return results;
}
3.4 组合使用
Generator 和 async/await 可以完美配合:
javascript
// 异步 Generator
async function* asyncGenerator() {
for (let i = 0; i < 5; i++) {
await new Promise(resolve => setTimeout(resolve, 1000));
yield i;
}
}
// 使用 for await...of 消费
async function consume() {
for await (const value of asyncGenerator()) {
console.log(value); // 每秒输出 0,1,2,3,4
}
}
// 实际应用:分页获取数据
async function* paginatedFetch(apiUrl) {
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await fetch(`${apiUrl}?page=${page}`);
const data = await response.json();
if (data.items.length === 0) {
hasMore = false;
} else {
yield data.items;
page++;
}
}
}
// 边获取边处理
async function processAllData() {
for await (const page of paginatedFetch('/api/users')) {
console.log(`处理第 ${page} 页数据`);
await processPage(page);
}
}
四、实战选择指南
✅ 使用 Generator 的场景
javascript
// 1. 实现可遍历的数据结构
class TreeNode {
*preOrder() {
yield this.value;
if (this.left) yield* this.left.preOrder();
if (this.right) yield* this.right.preOrder();
}
}
// 2. 惰性求值/懒加载
function* lazyRange(start, end) {
for (let i = start; i <= end; i++) {
yield expensiveCalculation(i); // 只在需要时计算
}
}
// 3. 有限状态机
function* doorStateMachine() {
let state = 'closed';
while (true) {
const action = yield state;
switch (state) {
case 'closed':
if (action === 'open') state = 'opened';
break;
case 'opened':
if (action === 'close') state = 'closed';
break;
}
}
}
// 4. 协同式多任务
function* task1() { /* ... */ }
function* task2() { /* ... */ }
function scheduler(tasks) {
while (tasks.length) {
const task = tasks.shift();
const result = task.next();
if (!result.done) tasks.push(task);
}
}
✅ 使用 async/await 的场景
javascript
// 1. 顺序异步操作
async function uploadFiles(files) {
for (const file of files) {
await uploadFile(file); // 等待上传完成
console.log(`${file.name} 上传成功`);
}
}
// 2. 并发控制
async function fetchAllUsers(ids) {
const promises = ids.map(id => fetch(`/api/users/${id}`));
const users = await Promise.all(promises);
return users;
}
// 3. 重试逻辑
async function fetchWithRetry(url, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fetch(url);
} catch (err) {
if (i === maxRetries - 1) throw err;
await sleep(1000 * Math.pow(2, i)); // 指数退避
}
}
}
// 4. 超时控制
async function fetchWithTimeout(url, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
return await fetch(url, { signal: controller.signal });
} finally {
clearTimeout(timeoutId);
}
}
五、总结表
| 对比项 | Generator | async/await |
|---|---|---|
| 声明 | function* |
async function |
| 暂停 | yield 任意值 |
await Promise |
| 执行 | 手动 .next() |
自动执行 |
| 返回值 | 迭代器 { value, done } |
Promise |
| 错误处理 | .throw() 外部注入 |
try/catch |
| 内存效率 | 极高(惰性) | 一般 |
| 适用场景 | 迭代、状态机、流处理 | API调用、异步流程 |
| 学习曲线 | 陡峭 | 平缓 |
| 代码简洁度 | 复杂(需执行器) | 简洁(原生支持) |
六、最佳实践建议
javascript
// 🎯 推荐组合使用
class DataProcessor {
// 异步获取原始数据
async fetchRawData() {
const response = await fetch('/api/data');
return response.json();
}
// Generator 处理大数据流
*processInChunks(data) {
const chunkSize = 1000;
for (let i = 0; i < data.length; i += chunkSize) {
yield data.slice(i, i + chunkSize);
}
}
// 组合使用
async process() {
const rawData = await this.fetchRawData();
for (const chunk of this.processInChunks(rawData)) {
await this.saveChunk(chunk); // 异步保存
}
}
}
最终结论:
- 日常开发:90% 的异步场景用 async/await
- 特殊需求:迭代器、状态机、大文件处理用 Generator
- 最佳实践:两者结合,发挥各自优势