- 文档/教程
- 前端
【免费下载链接】en.javascript.info
Modern JavaScript Tutorial
fetch()是现代浏览器内置的网络请求 API,允许 JavaScript 在不刷新页面的情况下向服务器发送请求、加载新信息。本文以《Modern JavaScript Tutorial》仓库中 5-network/01-fetch/article.md 为骨架,结合仓库内的服务端演示代码与配套练习题,系统讲解 fetch 的两阶段响应模型、响应体读取方法、请求/响应头操作、POST 请求、二进制数据上传,以及并发拉取 GitHub 用户信息的实战模式。读完本文,你将掌握用fetch完成从简单 GET 到表单、JSON、图片上传的完整网络通信方案。
一、为什么需要 fetch:AJAX 与网络请求
JavaScript 可以随时向服务器发送网络请求并加载新信息,典型场景包括:
- 提交订单;
- 加载用户信息;
- 从服务器接收最新更新;
- ……等等。
所有这些操作都不需要重新加载页面。这类"从 JavaScript 发起网络请求"的做法有一个统称AJAX(AsynchronousJavaScriptAndXML)。虽然名称里带有 XML,但如今并不强制使用 XML——这个词来自早期技术时代。你或许已经听说过它。
发送网络请求并获取服务器信息的方式有很多种,而fetch()是其中现代且功能全面的方法。它不被旧浏览器支持(但可以通过 polyfill 补充),在主流现代浏览器中支持度非常好。
二、fetch 基础语法与两阶段响应模型
fetch()的基本语法非常简洁:
let promise = fetch(url, [options])url—— 要访问的 URL;options—— 可选参数:method、headers 等。
如果不传options,则默认发起一个简单的 GET 请求,下载url的内容。浏览器会立即发起请求,并返回一个 Promise,调用方代码使用它来获取结果。
获取响应通常是一个两阶段的过程:
第一阶段:fetch返回的 Promise 在服务器响应头到达时立即 resolve,得到一个内置Response类的对象。在这一阶段,你可以检查 HTTP 状态码(判断请求是否成功)、查看响应头,但还拿不到响应体。
如果fetch无法发起 HTTP 请求(例如网络问题、站点不存在),Promise 会 reject。注意:异常的 HTTP 状态码(如 404、500)并不会导致 Promise 报错,它们只是普通的成功响应。
在响应对象上可以查看 HTTP 状态:
status—— HTTP 状态码,例如 200;ok—— 布尔值,HTTP 状态码为 200-299 时为true。
典型写法:
let response = await fetch(url); if (response.ok) { // HTTP-status 为 200-299 // 获取响应体(方法见下文) let json = await response.json(); } else { alert("HTTP-Error: " + response.status); }第二阶段:读取响应体需要额外的方法调用。
Response提供了多个基于 Promise 的方法,以不同格式读取响应体:
response.text()—— 将响应读取为文本;response.json()—— 将响应解析为 JSON;response.formData()—— 将响应作为FormData对象返回(详见下一章 FormData);response.blob()—— 将响应作为 Blob 返回(带类型的二进制数据);response.arrayBuffer()—— 将响应作为 ArrayBuffer 返回(二进制数据的底层表示);- 另外,
response.body是一个ReadableStream对象,允许你逐块读取响应体,后文会给出示例。
实战:从 GitHub API 拉取 JSON
例如,获取 GitHub 上最新提交的 JSON 对象:
let url = 'https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'; let response = await fetch(url); let commits = await response.json(); // 读取响应体并解析为 JSON alert(commits[0].author.login);同样的逻辑,不用await,使用纯 Promise 语法:
fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits') .then(response => response.json()) .then(commits => alert(commits[0].author.login));要获取响应文本,用await response.text()代替.json():
let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'); let text = await response.text(); // 将响应体读取为文本 alert(text.slice(0, 80) + '...');实战:以 Blob 方式下载并展示二进制内容
作为二进制格式读取的展示案例,仓库文章演示了下载 "fetch" 规范,关于Blob的操作详见 Blob 章节):
let response = await fetch('/article/fetch/logo-fetch.svg'); let blob = await response.blob(); // 作为 Blob 对象下载 // 为它创建 <img> let img = document.createElement('img'); img.style = 'position:fixed;top:10px;left:10px;width:100px'; document.body.append(img); // 展示它 img.src = URL.createObjectURL(blob); setTimeout(() => { // 三秒后隐藏 img.remove(); URL.revokeObjectURL(img.src); }, 3000);重要限制:只能选择一种响应体读取方法
注意:只能选择一种读取响应体的方法。
如果已经通过
response.text()获取了响应,那么response.json()将无法工作,因为响应体内容已被消费。let text = await response.text(); // 响应体已被消费 let parsed = await response.json(); // 失败(已消费)
三、Response headers:读取响应头
响应头位于response.headers,这是一个类似 Map 的对象。它不完全是 Map,但提供了类似的方法,可以按名称获取单个头或遍历所有头:
let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'); // 获取单个响应头 alert(response.headers.get('Content-Type')); // application/json; charset=utf-8 // 遍历所有响应头 for (let [key, value] of response.headers) { alert(`${key} = ${value}`); }四、Request headers:设置请求头与禁止头列表
要设置请求头,可以在fetch中使用headers选项,传入一个包含出站请求头的对象:
let response = fetch(protectedUrl, { headers: { Authentication: 'secret' } });但存在一份**禁止设置的 HTTP 头(forbidden HTTP headers)**列表,浏览器不允许你设置它们:
Accept-Charset、Accept-EncodingAccess-Control-Request-HeadersAccess-Control-Request-MethodConnectionContent-LengthCookie、Cookie2DateDNTExpectHostKeep-AliveOriginRefererTETrailerTransfer-EncodingUpgradeViaProxy-*Sec-*
这些头由浏览器独占控制,以确保 HTTP 的正确性与安全性。
五、POST 请求:提交 JSON 数据
要发起POST请求或其他方法,需要用到fetch的选项:
method—— HTTP 方法,例如POST;body—— 请求体,可以是以下之一:- 字符串(例如 JSON 编码的文本);
FormData对象,以multipart/form-data提交数据;Blob/BufferSource,用于发送二进制数据;- URLSearchParams,以
x-www-form-urlencoded编码提交数据,较少使用。
绝大多数情况下使用 JSON 格式。例如,下面的代码将user对象作为 JSON 提交:
let user = { name: 'John', surname: 'Smith' }; let response = await fetch('/article/fetch/post/user', { method: 'POST', headers: { 'Content-Type': 'application/json;charset=utf-8' }, body: JSON.stringify(user) }); let result = await response.json(); alert(result.message);请注意:如果请求body是字符串,Content-Type头默认会被设置为text/plain;charset=UTF-8。但由于我们要发送 JSON,所以通过headers选项显式改为application/json——这才是 JSON 编码数据正确的Content-Type。
仓库中的配套服务端实现
文章中的/article/fetch/post/user路由对应仓库里的演示服务 5-network/01-fetch/post.view/server.js。它是一个基于 Koa 的小型服务器,源码如下:
const Koa = require('koa'); const app = new Koa(); const bodyParser = require('koa-bodyparser'); const getRawBody = require('raw-body') const Router = require('koa-router'); let router = new Router(); router.post('/user', async (ctx) => { ctx.body = { message: "User saved." }; }); router.post('/image', async (ctx) => { let body = await getRawBody(ctx.req, { limit: '1mb' }); ctx.body = { message: `Image saved, size:${body.length}.` }; }); app .use(bodyParser()) .use(router.routes()) .use(router.allowedMethods()); if (!module.parent) { http.createServer(app.callback()).listen(8080); } else { exports.accept = app.callback(); }从源码可以看到:POST /user返回{ message: "User saved." },POST /image通过raw-body读取原始请求体(限制为 1MB),返回图片大小。这印证了文章示例中response.json()读到的result.message的来源;同时limit: '1mb'也提示了上传二进制数据的体量边界。该服务器默认监听 8080 端口,同时导出了accept回调供测试环境挂载。
六、发送二进制数据:用 Blob 上传图片
fetch也可以使用Blob或BufferSource对象提交二进制数据。
文章中的示例:页面上有一个<canvas>,可以通过移动鼠标在上面绘图,点击 "Submit" 按钮把图片发送到服务器:
<body style="margin:0"> <canvas id="canvasElem" width="100" height="80" style="border:1px solid"></canvas> <input type="button" value="Submit" onclick="submit()"> <script> canvasElem.onmousemove = function(e) { let ctx = canvasElem.getContext('2d'); ctx.lineTo(e.clientX, e.clientY); ctx.stroke(); }; async function submit() { let blob = await new Promise(resolve => canvasElem.toBlob(resolve, 'image/png')); let response = await fetch('/article/fetch/post/image', { method: 'POST', body: blob }); // 服务器返回确认信息和图片大小 let result = await response.json(); alert(result.message); } </script> </body>请注意:这里我们没有手动设置Content-Type头,因为Blob对象自带类型(这里由toBlob生成image/png)。对于Blob对象,这个类型会自动成为Content-Type的值。配合上一节的服务端代码,POST /image正是用getRawBody读取这个 Blob 的原始字节并返回其大小。
不使用async/await时,submit()函数可以改写成纯 Promise 形式:
function submit() { canvasElem.toBlob(function(blob) { fetch('/article/fetch/post/image', { method: 'POST', body: blob }) .then(response => response.json()) .then(result => alert(JSON.stringify(result, null, 2))) }, 'image/png'); }七、实战练习:并发拉取 GitHub 用户
仓库在 5-network/01-fetch/01-fetch-users/task.md 中提供了一个综合练习:创建一个异步函数getUsers(names),接收 GitHub 登录名数组,从 GitHub 拉取用户信息并返回用户数组。给定USERNAME的 GitHub 用户信息 URL 为https://api.github.com/users/USERNAME。关键要求:
- 每个用户发起一次
fetch请求; - 请求之间互不等待,让数据尽快到达;
- 如果某个请求失败或用户不存在,在结果数组中返回
null。
仓库中的参考解法见 5-network/01-fetch/01-fetch-users/solution.md,对应源码在 5-network/01-fetch/01-fetch-users/_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; }这个解法的精妙之处在于:.then直接挂在fetch上,这样一旦拿到响应就立即开始读取.json(),不会等待其他请求。如果改成await Promise.all(names.map(name => fetch(...)))再对结果调用.json(),就会等待所有 fetch 响应完成才逐一解析。把.json()直接接在每个fetch后面,可以保证每个请求独立地、尽早地开始按 JSON 读取数据。
配套测试 5-network/01-fetch/01-fetch-users/_js.view/test.js 验证了该行为:
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); }); });测试用例同时覆盖了正常用户(iliakan、remy)与不存在的用户(no.such.users应返回null)。这也是"即使主要使用async/await,底层 Promise API 依然有用"的典型示例。
八、总结
一个典型的 fetch 请求由两次await调用组成:
let response = await fetch(url, options); // 响应头到达即 resolve let result = await response.json(); // 读取 JSON 响应体或者不用await:
fetch(url, options) .then(response => response.json()) .then(result => /* 处理结果 */)响应属性:
response.status—— 响应的 HTTP 状态码;response.ok—— 状态码为 200-299 时为true;response.headers—— 类似 Map 的 HTTP 响应头对象。
读取响应体的方法:
response.text()—— 将响应作为文本返回;response.json()—— 将响应解析为 JSON 对象;response.formData()—— 将响应作为FormData对象返回(multipart/form-data编码,见下一章);response.blob()—— 将响应作为 Blob 返回(带类型的二进制数据);response.arrayBuffer()—— 将响应作为 ArrayBuffer 返回(底层二进制数据)。
目前介绍的 fetch 选项:
method—— HTTP 方法;headers—— 包含请求头的对象(并非所有头都允许设置);body—— 要发送的数据(请求体),可以是string、FormData、BufferSource、Blob或UrlSearchParams对象。
在后续章节中,本文所属的 网络请求目录 还会介绍fetch的更多选项与使用场景,例如 FormData 表单提交、下载进度、中断请求、跨源请求 等,本文介绍的两阶段模型、headers/body/method选项与 Promise 并发技巧是理解它们的基础。
- 文档/教程
- 前端
【免费下载链接】en.javascript.info
Modern JavaScript Tutorial
相关推荐
2025最全Fetch API实战指南:从基础到高级网络请求全解析
2025最全Fetch API实战指南:从基础到高级网络请求全解析 为什么现代前端开发离不开Fetch API? 你是否还在为XMLHttpRequest的回调
Modern JavaScript Tutorial 精读:Fetch 跨域请求与 CORS 全流程解密
Modern JavaScript Tutorial 精读:Fetch 跨域请求与 CORS 全流程解密 跨域请求(Cross Origin Request)是
文档/教程前端JavaScript 特性速览:Modern JavaScript Tutorial 基础语法全面回顾
JavaScript 特性速览:Modern JavaScript Tutorial 基础语法全面回顾 本篇指南对应 Modern JavaScript Tut
文档/教程前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考