异步生成器(async*)可将长任务拆分为可暂停的小块,通过yield按需产出、逐段消费,避免主线程阻塞。它返回AsyncIterator,支持for await...of,适用于大数据导出、文件分块读取等流式场景,兼顾响应性与逻辑连贯性。

长任务在 JS 项目中容易阻塞主线程,导致页面卡顿、响应迟滞。用异步生成器函数(async function*)做流式处理,核心是把大任务拆成小块、按需产出、逐段消费,既保持响应性,又维持逻辑连贯性。
用 async* 拆解长任务为可暂停的数据流
异步生成器不是一次性跑完全部逻辑,而是在每个 yield 处暂停,等待上一段结果被消费后再继续。它返回的是 AsyncIterator,天然支持 for await...of 循环,适合处理分片加载、滚动加载、实时日志、SSE 推送等场景。
- 适合场景:大数据导出、文件分块读取、定时轮询聚合、WebSocket 消息流、AI 响应流式吐出
- 关键点:每次
yield后必须是 Promise 或可 await 值,内部可自由await网络请求、定时器、计算等 - 示例:模拟一个耗时 5 秒的“数据处理”任务,但每 200ms yield 一项结果
在类中定义并使用异步生成器方法
类方法中直接声明 async * 是完全合法的,无需额外包装。调用时得到一个异步迭代器,可被外部驱动消费。
- 写法简洁:
async *processItems(items) { for (const item of items) { await delay(200); yield transform(item); } } - 调用方式自然:
for await (const result of instance.processItems(data)) { render(result); } - 优势明显:不占用主线程连续执行时间;消费方决定节奏(可暂停、跳过、提前终止);错误可集中 try/catch
配合背压控制避免内存堆积
流式处理若生产快于消费,中间结果可能堆积在内存中。异步生成器本身不自动限速,但可通过消费侧控制节奏实现背压。
- 简单做法:在
for await中加入await delay(0)让出微任务时机,给渲染或用户交互留空间 - 进阶做法:封装带缓冲上限的消费者,如
consumeWithLimit(iterator, maxPending = 3),超出则await当前项完成再拉下一项 - 注意:不要在生成器内部盲目
await Promise.all并发全量触发——这会失去流式意义,退化为批量异步
与事件循环协同,保障 UI 可响应
每次 yield 后,控制权交还给事件循环,浏览器有机会执行渲染、处理点击、响应动画帧。这是它优于普通 async/await 函数串行执行的关键。
- 对比:一个
async函数里循环 1000 次await fetch,虽异步但仍是宏任务排队,UI 仍可能卡顿 - 改进:改用
async*,每次只 fetch 一条并 yield,for await每次处理一条后就让出线程,UI 始终可响应 - 验证技巧:在 yield 前后加
console.timeLog,观察是否穿插了requestIdleCallback或raf的执行痕迹


















