前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
首页程序员面试题库异步生成器与普通生成器区别
JavaScript函数与作用域

异步生成器和普通生成器有什么区别?什么时候会用到它?

普通生成器的 next() 直接返回 { value, done },异步生成器的 next() 返回一个 Promise,要等它兑现才拿到结果。所以异步生成器里可以先 await 一个请求,再把结果 yield 出去,外面用 for await...of 一项一项地取。

前端进阶之旅 · 一题精讲更新于 2026.09.29
JavaScript#函数与作用域#异步编程
先看核心答案读代码示例
理解线索

能等待的「按需产出」

  1. await在生成器内部等请求、定时器或任何 Promise
  2. yield交出一个值然后暂停,等消费者要下一个
  3. for await...of自动调用 next() 并等待结果,提前退出时调用 return()

消费者不来取,生成器就停在 yield 那里不动。这是它和一次性把所有数据拉完的最大区别,数据源再大也不会一下子全堆进内存。

核心回答

先记住这个答案

普通生成器用 function* 声明,调用 next() 同步返回 { value, done }。异步生成器用 async function* 声明,函数体里既能 await 又能 yield,调用 next() 返回的是一个 Promise,兑现后才是 { value, done }。因为每一步都是异步的,普通的 for...of 用不了,要用 for await...of 来遍历。它最适合「数据要一段一段异步拿到」的场景,比如分页接口、流式读取文件。

  • 声明是 async function*,里面能同时写 await 和 yield
  • next() 返回 Promise,用 for await...of 消费
  • 取一项才往下跑一段,默认串行,不会自动并发
  • break 会触发生成器的 finally,适合在里面释放资源

next() 返回值的区别

先把两种生成器各调一次 next(),看拿到的是什么。

同一个 yield 1,两种返回JavaScript
function* gen() {
  yield 1
}
async function* agen() {
  yield 1
}
console.log(JSON.stringify(gen().next())) // 直接拿到结果
const p = agen().next()
console.log(p instanceof Promise) // 拿到的是 Promise
p.then((r) => console.log(JSON.stringify(r)))
查看输出与解释
{"value":1,"done":false}
true
{"value":1,"done":false}

普通生成器的 next() 立刻给出结果。异步生成器的 next() 只给一个 Promise,真正的结果要在 then 里或者 await 之后才拿到,所以最后一行最晚打印。 这就是两者最核心的区别。普通生成器里不能 await,因为 next() 必须同步返回结果,等不了。异步生成器把返回值包成了 Promise,才有了中间等一等的余地。

典型用法:分页接口逐条处理

比如一个列表接口一次返回一页,要一直拉到没有数据为止。用异步生成器把「怎么翻页」封装起来,调用方只管一条一条处理。

拉一页,吐一页,提前退出就停JavaScript
const request = (page) =>
  new Promise((r) => setTimeout(() => r([`p${page}-a`, `p${page}-b`]), 100)) // 模拟接口
async function* readPages() {
  try {
    for (let page = 1; page <= 3; page++) {
      const items = await request(page) // 等这一页回来
      yield* items // 逐条交出去
    }
  } finally {
    console.log('cleanup') // 正常结束或 break 都会走到这里
  }
}
async function main() {
  for await (const item of readPages()) {
    console.log(item)
    if (item === 'p2-a') break // 第 3 页根本不会请求
  }
  console.log('done')
}
main()
查看输出与解释
p1-a
p1-b
p2-a
cleanup
done

调用 readPages() 时函数体一行都不会执行,等 for await 第一次调用 next() 才开始跑,跑到 yield 就停下,把值交出去。消费者处理完一条,再要下一条,生成器才继续。拉取的节奏由消费者控制,这叫「拉模式」。 循环里 break 时,for await...of 会自动调用生成器的 return(),生成器从暂停的地方直接跳到 finally。所以关闭数据库游标、释放流的 reader 这类清理代码,放在 finally 里最稳。

它不会帮你并发

for await...of 是严格串行的:上一项的 Promise 兑现了,才去要下一项。上面的例子里,三页是一页一页请求的,总耗时是三次请求相加。

所以它和 Promise.all 是两种思路。Promise.all 是一次把请求全发出去,等全部回来;异步生成器是按需一项一项取,好处是能中途停、内存里只放当前这一页。如果想「边处理边预取下一页」,需要自己在生成器里提前发出下一页的请求,这不是语言自带的能力。

回答前,多想一步

容易答错的地方

忘了 next() 返回的是 Promise
手动调用异步生成器的 next(),拿到的是 Promise,直接读 .value 是 undefined。要 await 它,或者直接用 for await...of。
用普通 for...of 遍历异步生成器
异步生成器对象只实现了 Symbol.asyncIterator,没有 Symbol.iterator,用 for...of 会抛 TypeError,提示它不可迭代。
以为 for await 会并行执行
循环体里的每一轮都要等上一轮完成。想并行得自己用 Promise.all 或者手写一个带并发数的预取逻辑。
试着用自己的话回答

面试官还会怎么问?

异步生成器里 yield 一个 Promise 会怎样?

异步生成器会先等这个 Promise 兑现,再把兑现后的值交给消费者,所以 for await 拿到的是值本身。如果这个 Promise 拒绝,错误会在生成器内部 yield 的位置抛出,可以用 try...catch 接住。

for await...of 能遍历普通数组吗?

能。遍历同步可迭代对象时,它会对每一项做 await,所以数组里放一组 Promise 也能按顺序拿到结果。但这些 Promise 早就同时开始执行了,这里只是按顺序等,并不是按顺序发起。

异步生成器和 ReadableStream 怎么选?

业务层的分页遍历、轮询、按需加载,用异步生成器写起来最直接。处理字节流、需要 pipeThrough 串联转换、需要浏览器原生的背压和取消传播时,用 ReadableStream。Node.js 和较新的浏览器里,ReadableStream 本身也能用 for await...of 遍历,两者可以配合用,老浏览器要先确认兼容性。

从一道题,走向一组知识

把知识连起来

函数与作用域

JavaScript 传参是按值传递还是按引用传递?传对象进去改了会怎样?

生成器 yield 出去的对象和函数传参一样是共享引用,消费者改了它,生成器里也能看到。

函数与作用域

把对象方法传给 setTimeout,为什么 this 就丢了?怎么解决?

异步生成器常被写成类的方法,拆出来单独调用时同样会遇到 this 丢失的问题。

参考资料

  • JavaScript:函数与作用域参考资料

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. next() 返回值的区别
  3. 典型用法:分页接口逐条处理
  4. 它不会帮你并发
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑