Generator 全面解析 + async/await 深度对比

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
  • 最佳实践:两者结合,发挥各自优势
相关推荐
weixin_471383035 小时前
统一缩放单位基础(px、em、rem)
开发语言·javascript·ecmascript
yqcoder5 小时前
数据劫持的双雄:深入解析 Object.defineProperty 与 Proxy
开发语言·前端·javascript
lichenyang4535 小时前
鸿蒙聊天 Demo 练习 03:接入 Next.js 后端接口,实现真机前后端联调
前端
小三金5 小时前
EXPO+RN echarts图表库,以及如何使用
前端·javascript·react.js
ZFSS6 小时前
Midjourney Shorten API 的集成与使用
java·前端·数据库·人工智能·ai·midjourney·ai编程
Pu_Nine_96 小时前
IntersectionObserver 详解:封装 Vue 指令实现图片懒加载
前端·javascript·vue.js·性能优化
清灵xmf6 小时前
Web 和 Native 是怎么“对话“的?JSBridge 解答
前端·webview·native·jsbridge·hybrid
jiayong237 小时前
前端面试题库 - ES6+新特性篇
前端·面试·es6
海兰7 小时前
【实用应用】React+TypeScript+Next.js博客项目
开发语言·javascript·elasticsearch