async 生成器与 for await...of:实现异步数据流的“生产者-消费者”模式
核心要点:async 函数与异步迭代器结合,实质上就是明确分工的两部分——用 async function* 定义异步生成器来产出异步值,再用 for await...of 循环去消费它。前者是惰性、可暂停的数据源头,后者是自动等待的消费接口,两者配合,便能高效实现“边拉数据边处理”这一需求。

async function*:异步数据流的生成源头
它不仅保留了生成器那种“按需生成”的惰性特质,还能在内部使用 await 等待异步操作完成。每次 yield 后面可以跟一个 Promise 对象,生成器会自动暂停,等该 Promise resolve 后才继续下一轮循环。这背后有几层优势:
- 返回的对象会自动实现
Symbol.asyncIterator接口,无需手动构建迭代器协议; - yield 输出的值会被自动封装为
{ value: resolvedValue, done: false },由迭代器协议统一处理; - 函数体中可以混合同步逻辑(如计数、条件判断)与异步操作(如 fetch 请求、数据库查询),混用非常自然。
for await...of:消费端的标准化接口
该语法专为异步可迭代对象设计,自动调用 next() 方法,并自动 await 返回的 Promise,解构出 value 值。表面上看它和 for...of 一模一样,但背后是异步推进——每次迭代都会等待上一个 yield 的 Promise 完成,天然保证顺序执行。
不过有几个约束需要注意:
- for await...of 必须位于 async 函数或顶层模块作用域中,否则将引发语法错误;
- 每次迭代会等待前一个 yield 的 Promise 完成,因此不会出现并发乱序问题;
- 它支持 break、continue 以及 try/catch,错误可以在当前循环层级被捕获。
典型应用场景:边获取边处理,而非一次性加载
常见于分页请求、日志流、文件块读取等需要“边走边干”的场景。关键是将数据分批次拉取,而非全部加载到内存,让消费逻辑与数据到达的节奏对齐——数据来一点,就处理一点。
- 例如分页 API:每次 yield 时发起一页请求,消费者获取数组后即可立即渲染或校验,无需等待后续页面;
- 建议将错误处理置于异步生成器内部,用 try/catch 包裹 fetch 操作,避免整个流中断;也可以在 for await 循环中使用 try/catch 捕获,决定是否继续遍历或提早中止;
- 如需提前退出(例如已找到目标项),直接使用 break 即可。此时异步生成器会触发 return() 方法,可用于清理资源(如关闭文件流或取消请求)。
兼容性与常见陷阱:避免这些细节问题
该特性自 ES2018 起获得原生支持,现代浏览器及 Node.js v10 以上版本已全面支持,但老旧环境需要转译(如 Babel)。实际编码中容易踩的几个坑:
- for await...of 不能出现在普通函数中,必须包裹在 async 函数内部;
- 在 forEach、map 等高阶函数回调中直接使用 await 会破坏迭代顺序,正确做法是使用 for await...of 循环进行消费;
- yield null 或 undefined 不会影响迭代器协议,但只有当 yield 的值是 Promise 时,迭代器才会自动等待其 resolve。如果你 yield 了一个非 Promise 的异步结果(比如一个需要手动 await 的表达式),它不会自动变成异步值——要么 yield 一个已经 resolve 的值,要么 yield await promise 来明确等待。
总而言之,async 生成器与 for await...of 的组合构成了标准的生产者-消费者模式,适用于异步场景下的“拉取式”数据流。理解这一本质后,编写的代码将更加简洁且可控。
