文档/教程前端【免费下载链接】en.javascript.infoModern JavaScript Tutorial项目地址https://gitcode.com/gh_mirrors/en/en.javascript.info点击查看免费下载在 Web 前端开发中“一次性并发请求多个资源”是最常见的实战场景之一。本文基于 Modern JavaScript Tutorialen.javascript.info中 Fetch 章节 的经典任务getUsers完整讲解如何用fetchPromise.all批量、并发地从 GitHub API 拉取多个用户的公开信息并正确处理网络失败与不存在的用户。读完本文你将掌握async/await与底层 Promise API 混用的关键技巧理解“何时该把.then直接挂在fetch上”这一并发优化的本质。任务概述从任务描述到实现目标本任务源自仓库中的 任务描述文件要求实现一个异步函数async function getUsers(names)输入一个 GitHub 登录名login数组例如[iliakan, remy, no.such.users]输出一个与输入顺序对应的 GitHub 用户对象数组数据来源单个用户的公开信息通过如下 REST 接口获取https://api.github.com/users/USERNAME其中USERNAME替换为具体的登录名。接口返回的是标准的 JSON 用户对象含login、id、name、avatar_url等字段。任务对实现提出了三条必须同时满足的硬性要求每个用户恰好发起一次fetch请求不能复用、合并或重复请求请求之间互不等待数据要“尽可能快地到达”——即请求必须并发发出而不是串行排队容错兜底任何请求失败如网络中断或该用户不存在HTTP 404对应的结果位置必须返回null而不是让整个函数报错。这三条要求分别对应并发度、延迟与健壮性三个维度也是本任务真正的考察重点。前置知识fetch 的两阶段响应模型在动手写代码之前需要先厘清fetch的工作方式。如 Fetch 章节 所讲一次fetch的响应过程分为两个阶段第一阶段响应头到达。fetch(url)返回的 Promise 在服务器返回响应头时即告 resolve此时可以读取 HTTP 状态码response.status—— HTTP 状态码如200、404response.ok—— 布尔值status在 200–299 之间时为true。注意只有网络层失败无网络、域名不存在等才会导致fetch的 Promise rejectHTTP 404、500 这类异常状态码并不会 reject而是以正常 resolve 的方式携带非 2xx 的状态码返回。第二阶段读取响应体。需要额外调用一个方法例如response.json()把响应体解析为 JavaScript 对象。该方法本身也返回一个 Promise在响应体读取并解析完成后 resolve。因此典型的fetch用法是两次awaitlet response await fetch(url); // 第一阶段拿到响应头与状态码 let user await response.json(); // 第二阶段读取并解析响应体这一“两阶段”模型是本任务解法的基石尤其是第二阶段的response.json()何时开始执行直接决定了请求是“并发读数据”还是“串行等结果”。参考解法完整代码与逐段拆解仓库在 参考解法文档 和沙箱目录 _js.view/solution.js 中给出了标准答案完整代码如下async function getUsers(names) { let jobs []; for(let name of names) { let job fetch(https://api.github.com/users/${name}).then( successResponse { if (successResponse.status ! 200) { return null; } else { return successResponse.json(); } }, failResponse { return null; } ); jobs.push(job); } let results await Promise.all(jobs); return results; }逐段拆解如下第一步为每个名字创建一个“作业”Promise。用for..of遍历输入数组对每个登录名拼接出对应 URL 并立即发起fetchlet job fetch(https://api.github.com/users/${name}).then(...);第二步在.then中同时处理成功与失败两个分支。.then( successResponse { if (successResponse.status ! 200) { return null; } else { return successResponse.json(); } }, failResponse { return null; } )successResponse分支当响应头到达时执行。若status ! 200包括 404 等直接返回null否则返回successResponse.json()—— 注意这里返回的是一个Promise它会在响应体解析完成后 resolve 为用户对象failResponse分支对应fetch的 Promise 被 reject网络层失败返回null。.then(onFulfilled, onRejected)双参数写法在这里很关键它把“HTTP 非 200”和“网络失败”两条错误路径统一归一化为null值避免了任何 Promise 处于 rejected 状态。第三步用Promise.all聚合所有作业。let results await Promise.all(jobs); return results;Promise.all接收 Promise 数组返回一个新 Promise在所有作业都 settle无论成功失败后 resolve 为一个数组数组顺序与输入顺序一致。由于每个作业要么 resolve 成用户对象、要么 resolve 成nullPromise.all永远不会收到 rejection因此不会因某个用户不存在而中断整个批量任务。并发是核心为什么把.then直接挂在fetch上任务第二条要求“请求不应互相等待”而解法文档特别强调了一个容易踩坑的写法差异。先看“错误”的写法直觉新手往往这样写let responses await Promise.all(names.map(name fetch(https://api.github.com/users/${name}))); let results responses.map(response response.json());这段代码虽然让fetch并发发出但第二步对responses逐项调用response.json()时每个response的响应体读取会等待所有fetch的响应头全部返回之后才开始。也就是说Promise.all(names.map(...))这一行会先等待所有请求“响应头阶段”完成才能进入“读响应体”阶段。响应体是数据的主体这样整体上数据到达的时机被推迟了。参考解法的精妙之处在于response.json()的调用被直接放在每个fetch自己的.then回调里响应头一到达就立刻开始读取该响应的响应体无需等待其他任何请求let job fetch(url).then(successResponse successResponse.json());于是每个作业的“网络传输 JSON 解析”是各自独立并行的请求 A 的响应体还没读完时请求 B 的响应体可能已经解析完成。最终Promise.all只负责收集结果不负责串行化任何工作。这正是解法文档末尾的结语所点明的主题即使主力写法是async/await底层 Promise API 依然有不可替代的用武之地。这里用到的知识点then回调、Promise.all、Promise 的 resolve/reject 语义都可以在仓库的 Promise 基础章节 与 Promise 链式调用章节 中找到完整理论支撑。错误处理与边界情况任务第三条要求所有失败场景统一输出null参考解法覆盖了两种完全不同的失败来源失败来源触发条件处理位置结果网络层失败断网、DNS 解析失败、请求被拒等fetch的 Promise reject进入.then的第二个回调nullHTTP 非 200用户不存在404、限流403等响应头到达successResponse.status ! 200null两种路径都必须处理。特别提醒HTTP 404 不会让fetchreject如果只在.then的成功回调里返回response.json()那么不存在的用户会得到一个 404 的响应体解析结果通常是{message: Not Found, ...}之类而不是null从而破坏测试断言。因此显式检查status ! 200是必不可少的一步。此外正因为每个作业内部已经把所有可能的 rejection 转换成了nullPromise.all才得以安全使用——否则任何一个用户的网络抖动都会让整个getUsers抛错。沙箱与测试验证仓库为本题提供了完整的可运行沙箱位于 5-network/01-fetch/01-fetch-users/_js.view/ 目录source.js —— 预置的题目骨架只有async function getUsers(names) { /* your code */ }留给读者实现solution.js —— 上文展示的参考解答test.js —— 官方单元测试使用 Mocha 断言describe(getUsers, function() { it(gets users from GitHub, async function() { let users await getUsers([iliakan, remy, no.such.users]); assert.equal(users[0].login, iliakan); assert.equal(users[1].login, remy); assert.equal(users[2], null); }); });测试用例精确验证了任务的三条要求users[0].login iliakan与users[1].login remy真实存在的用户被正确解析为 GitHub 用户对象users[2] null不存在的用户no.such.users在结果数组的对应位置得到null断言按数组下标进行隐含地验证了结果顺序与输入顺序一致这正是Promise.all的保证。该测试同时涵盖了“成功”与“失败”两条路径说明实现必须同时通过这两类断言才算正确。仓库中关于 Mocha 测试框架的完整用法可参考 测试章节。延伸等价的map写法与更精细的错误处理参考解法用for..of循环收集作业逻辑清晰、便于逐行讲解。作为延伸用map可以写得更紧凑语义完全等价async function getUsers(names) { let results await Promise.all( names.map(name fetch(https://api.github.com/users/${name}).then( response response.status 200 ? response.json() : null, () null ) ) ); return results; }两者本质相同每个fetch自带.then把异常统一收敛为null再由Promise.all聚合。如果希望把“网络失败”与“HTTP 非 200”区分对待例如前者重试、后者直接判空可以分别使用.catch()与状态码检查而参考解法用.then的双参数形式一次处理两条路径代码更紧凑。总结getUsers是一个小而精的并发实战任务它串起了fetch两阶段响应模型、.then回调、Promise.all聚合、async/await语法以及错误归一化这几大知识点每个用户一个fetch用for..of或map批量发起.then直接挂在fetch上让每个请求的响应体读取立即并行开始这是满足“互不等待”要求的关键status ! 200返回null覆盖“用户不存在”等 HTTP 层失败rejected 分支返回null覆盖网络层失败并保证Promise.all永远不会收到 rejectionawait Promise.all(jobs)保持结果顺序等待全部作业完成后一次性返回。理解了这个任务你就掌握了在async/await时代仍然需要底层 Promise 技巧的核心场景凡是要“并发发起、各自消化、统一聚合”的请求都值得套用这一模式。更多fetch的选项method、headers、body等可继续阅读 Fetch 章节 与仓库中 网络请求章节索引 下的其他文章。赞分享文档/教程前端【免费下载链接】en.javascript.infoModern JavaScript Tutorial项目地址https://gitcode.com/gh_mirrors/en/en.javascript.info点击查看免费下载相关推荐将 JavaScript 提取为外部脚本以 Modern JavaScript Tutorial 的 alert 任务为例将 JavaScript 提取为外部脚本以 Modern JavaScript Tutorial 的 alert 任务为例 在 Web 开发中把 JavaS文档/教程前端Modern JavaScript Tutorial 实战用 insertAdjacentHTML 向列表插入 HTML —— append-to-list 任务全解析Modern JavaScript Tutorial 实战用 insertAdjacentHTML 向列表插入 HTML —— append to list文档/教程前端把10块钱的鼠标用出触控板的感觉Mac Mouse Fix 完整上手指南把10块钱的鼠标用出触控板的感觉Mac Mouse Fix 完整上手指南 刚换了新 Mac你第一句抱怨大概率是这个鼠标真难用。滚动一格一格地跳侧键按下去文档/教程前端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?