← 返回题目列表

异步迭代器和 for await...of 适合解决什么问题?

中等 第 19 / 28 题 更新于 2026/07/29
ES6异步迭代器for-await-of

简化版

异步迭代器让数据可以一批一批、异步地产生。for await...of 可以按顺序消费异步可迭代对象,常见于流式数据、分页拉取、Web Stream、Node Stream 等场景。

它和普通 for...of 的区别是:每次取值可能需要等待 Promise,因此天然适合处理“数据未来才到”的序列。

详细版

async function* pages() {
  yield await fetchPage(1);
  yield await fetchPage(2);
}

for await (const page of pages()) {
  console.log(page);
}

异步可迭代对象需要实现 Symbol.asyncIterator

const source = {
  async *[Symbol.asyncIterator]() {
    yield 1;
    yield 2;
  }
};

for await...of 会等待每次 next() 的结果,适合顺序消费,不适合默认并发。

完整版教学

一、为什么需要异步迭代

普通数组一次性存在内存中,可以同步遍历。

但接口分页、文件流、消息流不是一次性到达的。

time ->
page1 ---- page2 ---- page3

异步迭代器把“未来陆续到来的数据”抽象成可遍历序列。

异步迭代不是为了替代 Promise,而是为了表达多个异步结果组成的序列。

二、同步迭代和异步迭代的接口差异

同步迭代使用 Symbol.iteratornext() 返回普通结果。

异步迭代使用 Symbol.asyncIteratornext() 返回 Promise。

类型协议next 返回
同步可迭代Symbol.iterator{ value, done }
异步可迭代Symbol.asyncIteratorPromise<{ value, done }>

这就是 for await...of 需要 await 的原因。

三、async generator 是最常见写法

async function* getUsers() {
  let page = 1;

  while (page <= 3) {
    const users = await fetchUsers(page);
    yield users;
    page += 1;
  }
}

async function* 同时具备 async 和 generator 的能力:可以 await,也可以 yield

四、for await…of 顺序消费

for await (const users of getUsers()) {
  render(users);
}

循环会等待当前 next() 完成,再进入下一轮。它表达的是顺序消费,而不是并发请求。

如果要并发,应该先收集任务,再使用 Promise.all 或并发控制器。

五、提前退出会触发 return

breakthrow 或函数返回导致循环提前结束时,迭代器的 return 方法可能被调用,用于清理资源。

async function* stream() {
  try {
    yield 1;
    yield 2;
  } finally {
    console.log("cleanup");
  }
}

这对流和订阅类资源很重要。

六、典型业务场景

常见场景:

  • 分页接口逐页加载
  • ReadableStream 逐块读取
  • 日志流逐条处理
  • 大文件分片处理
  • 消息队列消费
let count = 0;
for await (const chunk of stream) {
  count += chunk.length;
}

数字上看,10MB 文件按 64KB chunk 处理,大约会产生 160 次迭代,不需要一次性把全部内容塞进内存。

七、常见误区与追问

  • 误区:for await…of 会自动并发执行。 它默认按顺序等待每次迭代。
  • 误区:异步迭代器只能来自 async generator。 手写 Symbol.asyncIterator 也可以。
  • 误区:一个 Promise 数组必须用 for await。 多数并发场景更适合 Promise.all
  • 误区:提前 break 不需要考虑清理。 迭代器可能持有流、连接或订阅资源。
  • 追问:它和普通 for…of 最大区别是什么? 每次 next 可以是异步的。
  • 追问:分页接口为什么适合 async generator? 它能把请求下一页和产出当前页封装在同一个序列里。

八、加强记忆

记一张图:

async source -> asyncIterator.next() -> Promise<result>
                            |
                      for await consumes

回答时先说异步序列,再说协议差异,最后落到流式读取、分页加载和顺序消费。这样比只解释语法更像真实项目经验。