深入实战)
文档/教程前端【免费下载链接】en.javascript.infoModern JavaScript Tutorial项目地址https://gitcode.com/gh_mirrors/en/en.javascript.info点击查看免费下载异步迭代Asynchronous Iteration让我们能够按需、分批地消费异步到达的数据——例如通过网络分块下载内容而异步生成器Async Generators则把这种边取边用的写法进一步简化。本文基于 Modern JavaScript Tutorialen.javascript.info 仓库中 异步迭代与异步生成器章节 展开从同步可迭代对象与生成器回顾出发逐步过渡到Symbol.asyncIterator、for await..of、async function*并以 GitHub 分页提交列表的真实场景收尾。读完本文你将掌握如何让对象支持异步迭代、如何用异步生成器封装分页/流式数据以及何时该用for await..of而非for..of。先回顾可迭代对象Iterables在进入异步世界之前先回顾同步可迭代对象的核心机制。所谓可迭代就是让一个普通对象能被for..of循环遍历。假设我们有一个表示数字区间的range对象let range { from: 1, to: 5 };我们希望for(value of range)能依次给出1到5。这需要给对象加上一个名为Symbol.iterator的特殊方法for..of循环开始时调用该方法一次它应返回一个带next方法的对象每一次迭代循环都会调用next()获取下一个值next()必须返回形如{done: true/false, value: 循环值}的对象其中done:true表示循环结束。range的完整实现如下let range { from: 1, to: 5, [Symbol.iterator]() { // 在 for..of 开始时被调用一次 return { current: this.from, last: this.to, next() { // 每次迭代被调用返回下一个值 if (this.current this.last) { return { done: false, value: this.current }; } else { return { done: true }; } } }; } }; for(let value of range) { alert(value); // 依次输出 1, 2, 3, 4, 5 }细节可参阅仓库中的 可迭代对象Iterables章节那里完整讲解了Symbol.iterator的协议约定next()结果必须为{done: Boolean, value: any}donetrue表示循环结束。异步可迭代对象Async Iterables当值不是同步可得而是需要等待setTimeout、网络请求等异步操作后才能拿到时就需要异步迭代。最常见的场景是对象每产出下一个值都要先发起一次网络请求下文的分页示例即属此类。把一个对象改造成异步可迭代只需三步使用Symbol.asyncIterator代替Symbol.iteratornext()方法要返回一个Promise并在其 resolve 时携带下一个值——用async关键字声明即可也就是async next()遍历时使用for await (let item of iterable)循环——注意for后面的await关键字。把之前的range改造成每秒异步产出一个值的版本只需做少量替换let range { from: 1, to: 5, [Symbol.asyncIterator]() { // (1) 使用异步迭代符号 return { current: this.from, last: this.to, async next() { // (2) next 返回 Promise // 注意在 async next 内部可以直接使用 await await new Promise(resolve setTimeout(resolve, 1000)); // (3) 模拟延迟 1 秒 if (this.current this.last) { return { done: false, value: this.current }; } else { return { done: true }; } } }; } }; (async () { for await (let value of range) { // (4) 使用 for await..of alert(value); // 依次输出 1,2,3,4,5每个间隔 1 秒 } })()结构与同步迭代器高度相似差异要点如下要让对象支持异步迭代必须提供Symbol.asyncIterator方法(1)该方法返回的对象其next()方法返回 Promise(2)next()不一定要写成async也可以是返回 Promise 的普通方法但async允许在内部直接await所以更方便——示例中就用它等待了 1 秒(3)遍历时用for await(let value of range)(4)即在for之后加await。它先调用一次range[Symbol.asyncIterator]()再逐个调用其next()取回值。同步迭代器与异步迭代器对照表迭代器Iterators异步迭代器Async iterators提供迭代器的对象方法Symbol.iteratorSymbol.asyncIteratornext()的返回值任意值Promise遍历语法for..offor await..of警告展开语法...不支持异步凡是依赖同步迭代器Symbol.iterator的特性都无法作用于异步可迭代对象。例如展开语法会直接报错alert( [...range] ); // Error, no Symbol.iterator这很自然——展开语法寻找的是Symbol.iterator而不是Symbol.asyncIterator。同理不带await的for..of也只认Symbol.iterator。再回顾生成器Generators生成器能让迭代代码大幅缩短绝大多数场景下我们都会用生成器来实现可迭代对象。简单来说生成器就是能产出yield多个值的函数。它们用function*声明注意星号用yield产出值然后用for..of遍历。生成器细节见仓库中的 生成器章节。一个生成start到end序列的例子function* generateSequence(start, end) { for (let i start; i end; i) { yield i; } } for(let value of generateSequence(1, 5)) { alert(value); // 依次输出 1, 2, 3, 4, 5 }前面讲过让对象可迭代要给它加Symbol.iterator。常见做法是让Symbol.iterator返回一个生成器代码会简洁很多let range { from: 1, to: 5, *[Symbol.iterator]() { // 等价于 [Symbol.iterator]: function*() for(let value this.from; value this.to; value) { yield value; } } }; for(let value of range) { alert(value); // 依次输出 1, 2, 3, 4, 5 }之所以可行是因为range[Symbol.iterator]()现在返回的是一个生成器而生成器恰好满足for..of的全部期待有.next()方法且返回值是{value: ..., done: true/false}形态。这不是巧合——生成器从设计之初就是为配合迭代器而生的。但同步生成器内部不能使用awaitfor..of要求所有值同步产出。如果数据本身是异步的比如来自网络请求就需要切换到异步生成器。异步生成器Async Generators在绝大多数实用场景中想要一个异步产出值序列的对象直接使用异步生成器即可。语法极其简单在function*前面加上async前缀生成器就变成异步的。然后用for await (...)遍历async function* generateSequence(start, end) { for (let i start; i end; i) { // 看内部可以直接 await await new Promise(resolve setTimeout(resolve, 1000)); yield i; } } (async () { let generator generateSequence(1, 5); for await (let value of generator) { alert(value); // 依次输出 1,2,3,4,5每个间隔 1 秒 } })();因为生成器是异步的内部可以放心使用await、依赖 Promise、发起网络请求等。底层差异Under-the-hood对熟悉生成器机制的进阶读者补充一点内部差异对于异步生成器generator.next()方法本身是异步的返回的是 Promise。同步生成器里我们用result generator.next()取值异步生成器里要加awaitresult await generator.next(); // result {value: ..., done: true/false}这正是异步生成器能配合for await...of工作的根本原因。异步可迭代的 range同步生成器可以充当Symbol.iterator来精简迭代代码同理异步生成器可以充当Symbol.asyncIterator来实现异步迭代。把前面同步的range换成每秒产出一次值的异步版本let range { from: 1, to: 5, // 这行等价于 [Symbol.asyncIterator]: async function*() { async *[Symbol.asyncIterator]() { for(let value this.from; value this.to; value) { // 在两个值之间停顿等待某件事完成 await new Promise(resolve setTimeout(resolve, 1000)); yield value; } } }; (async () { for await (let value of range) { alert(value); // 依次输出 1,2,3,4,5每个间隔 1 秒 } })();现在每个值之间都间隔 1 秒。技术上也可以同时给对象挂上Symbol.iterator和Symbol.asyncIterator让它既能被for..of同步遍历、也能被for await..of异步遍历——不过实际工程中这样做很反常一般不建议。实战案例分页数据Paginated Data前面的例子用于建立直觉现在看一个真实场景。许多在线服务以分页方式返回数据比如请求用户列表时一次返回固定数量例如 100 个用户作为一页并在响应中附带下一页的 URL。这种模式极其普遍不局限于用户列表几乎适用于任何数据。以 GitHub 为例它允许以同样的分页方式获取提交记录向https://api.github.com/repos/repo/commits发起fetch请求响应返回包含 30 条提交的 JSON同时在Link响应头中给出下一页的链接用该链接发起下一次请求获取更多提交如此反复。我们希望有一个更省心的方式获取提交。定义一个fetchCommits(repo)函数由它负责处理所有分页细节对外呈现为一个简单的异步可迭代对象。这样调用方只需写for await (let commit of fetchCommits(username/repository)) { // 处理 commit }用异步生成器实现如下async function* fetchCommits(repo) { let url https://api.github.com/repos/${repo}/commits; while (url) { const response await fetch(url, { // (1) 发起请求 headers: {User-Agent: Our script}, // github 要求携带 user-agent 头 }); const body await response.json(); // (2) 响应体是 JSON提交数组 // (3) 下一页的 URL 在响应头里提取它 let nextPage response.headers.get(Link).match(/(.*?); relnext/); nextPage nextPage?.[1]; url nextPage; for(let commit of body) { // (4) 逐条 yield 提交直到本页结束 yield commit; } } }逐点解释其工作机制使用浏览器原生fetch方法下载提交数据。初始 URL 是https://api.github.com/repos/repo/commits下一页链接来自响应的Link头。fetch允许携带鉴权及其他请求头——这里 GitHub 要求必须有User-Agent。fetch的详细用法见仓库中的 Fetch 章节其基本语法为let promise fetch(url, [options])响应需分两阶段处理先拿到含 HTTP 状态与响应头的Response对象再通过response.json()等方法读取响应体提交数据以 JSON 数组形式返回从响应的Link头中解析下一页 URL。该头部有特定格式因此使用正则表达式提取正则的完整知识见 正则表达式章节。下一页 URL 形如https://api.github.com/repositories/93253246/commits?page2由 GitHub 自行生成把收到的提交逐条yield出去当本页数据耗尽时while(url)会进入下一轮循环再次发起请求获取下一页。实际使用示例在控制台打印提交作者(async () { let count 0; for await (const commit of fetchCommits(javascript-tutorial/en.javascript.info)) { console.log(commit.author.login); if (count 100) { // 取满 100 条就停止 break; } } })(); // 注意如果在外部沙箱中运行需要先把上文 fetchCommits 函数粘贴进来这正是我们想要的效果分页请求的内部机制对外完全不可见调用方眼里它只是一个不断产出提交的异步生成器。仓库佐证该示例脚本在教程仓库中被单独抽取为可运行的 HTML 头部脚本见 head.html其中fetchCommits的实现与上文完全一致含User-Agent请求头、Link头正则提取、yield commit逐条产出。该章节所属的 生成器与高级迭代目录 也由此示例与同步生成器章节共同构成完整知识链同步生成器内容可对照 1-generators/article.md涵盖function*、yield、next()的{value, done}返回协议、生成器作Symbol.iterator、无限生成器与break等而本文正是在其基础上叠加async语义。小结同步迭代器与生成器适合处理生成不需要耗时的数据当数据异步到达、带延迟时改用其异步版本并用for await..of代替for..of。异步与同步迭代器的语法差异可迭代对象Iterable异步可迭代对象Async Iterable提供迭代器的方法Symbol.iteratorSymbol.asyncIteratornext()的返回值{value:…, done: true/false}解析为{value:…, done: true/false}的Promise异步与同步生成器的语法差异生成器Generators异步生成器Async generators声明function*async function*next()的返回值{value:…, done: true/false}解析为{value:…, done: true/false}的PromiseWeb 开发中我们经常面对数据分块流动的流式场景例如大文件的上传与下载。异步生成器正是处理此类数据的利器。值得一提的是在某些环境如浏览器中还有另一套名为Streams的 API它提供专门的接口来处理这类流——包括数据转换、以及把数据从一个流直接导到另一个流例如边下载边转发可作为进一步学习的延伸方向。赞分享文档/教程前端【免费下载链接】en.javascript.infoModern JavaScript Tutorial项目地址https://gitcode.com/gh_mirrors/en/en.javascript.info点击查看免费下载相关推荐3个核心模块如何构建企业级语音交互系统从零到一的技术实现指南3个核心模块如何构建企业级语音交互系统从零到一的技术实现指南 在数字化浪潮席卷各行各业的今天语音交互系统已成为企业客户服务、智能助手和自动化流程的关键入口。人工智能AI Agent语音多模态后端WebRTC交互助手如何优化Qwen3.6-27B-Uncensored-HauhauCS-Balanced推理性能温度、top_p等参数设置技巧如何优化Qwen3.6 27B Uncensored HauhauCS Balanced推理性能温度、top_p等参数设置技巧 Qwen3.6 27B UncAgent-Native 智能体应用实战指南从环境搭好到上线发布的五段式完整路径Agent Native 智能体应用实战指南从环境搭好到上线发布的五段式完整路径 用 Agent Native 做智能体应用很多人卡在两个地方一是目录一乱文档/教程前端上一篇终极指南如何客观衡量Vibe语音识别准确率的完整方法下一篇EM-Synchrony实战案例构建高性能异步Web爬虫创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考