Skip to content

异步迭代

异步迭代用于处理“值会陆续到达”的场景。和普通迭代不同,它不是立刻拿到下一个值,而是先等待一个 Promise 完成,再继续取值。

什么场景会用到

更常见的情况是:

  • 分批读取远程数据
  • 按流逐段消费内容
  • 持续接收异步事件
  • 边等待边处理,而不是一次性拿完整结果

先分清几个概念

  • iterable:通过 Symbol.iterator 提供同步迭代
  • async iterable:通过 Symbol.asyncIterator 提供异步迭代
  • iterator:每次调用 next() 返回一个结果对象
  • async iterator:每次调用 next() 返回一个 Promise

可以先记成:普通迭代是“直接给结果”,异步迭代是“先等,再给结果”。

异步可迭代对象

如果一个对象实现了 Symbol.asyncIterator,它就可以被 for await...of 消费:

js
const range = {
  from: 1,
  to: 3,
  [Symbol.asyncIterator]() {
    let current = this.from
    const end = this.to

    return {
      async next() {
        await new Promise((resolve) => setTimeout(resolve, 300))

        if (current <= end) {
          return { value: current++, done: false }
        }

        return { value: undefined, done: true }
      },
    }
  },
}

这里的关键区别在于:next() 不再直接返回 { value, done },而是返回一个 Promise。

for await...of

消费异步可迭代对象时,通常使用 for await...of

js
;(async () => {
  for await (const value of range) {
    console.log(value) // 1 2 3
  }
})()

它的执行过程可以粗略理解成:

  1. 调用 obj[Symbol.asyncIterator]()
  2. 拿到异步迭代器
  3. 反复 await iterator.next()
  4. done === true 时结束

手动调用 next()

异步迭代器也可以手动消费:

js
const iterator = range[Symbol.asyncIterator]()

iterator.next().then(console.log)
iterator.next().then(console.log)

只是大多数场景下,for await...of 更直接。

异步生成器

如果要按顺序异步地产出一批值,通常可以直接使用异步生成器:

js
async function* generateSequence(start, end) {
  for (let i = start; i <= end; i++) {
    await new Promise((resolve) => setTimeout(resolve, 300))
    yield i
  }
}

它相当于把“异步逻辑”和“生成器语法”结合在一起。

异步生成器也能直接遍历

js
;(async () => {
  for await (const value of generateSequence(1, 3)) {
    console.log(value) // 1 2 3
  }
})()

这通常比手写 Symbol.asyncIterator + next() 更简洁。

和普通生成器的区别

普通生成器:

js
function* gen() {
  yield 1
  yield 2
}

异步生成器:

js
async function* gen() {
  yield 1
  yield 2
}

主要差别不是 yield 本身,而是消费方式:

  • 普通生成器配合 for...of
  • 异步生成器配合 for await...of

和 Promise 数组的区别

这也是一个常见混淆点。

js
const list = [Promise.resolve(1), Promise.resolve(2)]

这只是“数组里放了 Promise”,不等于异步可迭代对象本身就是分段产生值。

异步迭代更强调:

  • 值按顺序陆续到来
  • 每次取下一个值都可能要等待
  • 不一定一开始就拥有完整集合

什么时候适合用异步迭代

更适合:

  • 数据天然按流到达
  • 需要逐条消费异步结果
  • 不希望一次性把所有结果放进内存
  • 处理过程需要边读边做

如果只是 await fetch() 后拿一个完整数组,再普通遍历,通常没必要上异步迭代。

使用建议

  • 需要“逐步异步地产出值”时,再考虑异步迭代。
  • 优先使用 async function*,通常比手写异步迭代器更清晰。
  • 分清“Promise 数组”和“异步可迭代对象”不是同一回事。
  • 看到 for await...of 时,先确认对象是否实现了 Symbol.asyncIterator

基于 MIT 许可发布