Fetch 是基于 Promise 的 HTTP 客户端
fetch 内置于浏览器和新版 Node 中。你给它一个 URL,它返回一个 Promise,resolve 的值是一个 Response 对象。核心 API 就这么简单:
之所以有两次 .then 调用,是因为有两个异步步骤:先是响应头到达(第一个 promise resolve 的就是它),然后读取并解析响应体(response.json() 本身也是一个 promise)。在你主动请求之前,响应体并不会被下载。
同样的流程用 async/await 来写,读起来就像普通的从上到下的代码:
两个 await,两个暂停点。做的事情一样,阅读顺序却清楚得多。
Response 对象
你拿到的不是响应体,而是一个 Response 对象,它包含元数据,以及以不同形式读取响应体的方法:
你可以用 .json()、.text()、.blob()、.arrayBuffer() 或 .formData() 读取响应体,每个方法都返回 promise。响应体只能读取一次:对同一个响应调用两次 .json(),第二次调用会抛出异常。
最大的坑:HTTP 错误不会 reject
几乎每个刚接触 fetch 的人都会在这里栽跟头。404 或 500 响应并不会导致 reject。promise 会正常 resolve,只是 response.ok === false。只有当请求本身无法完成时,fetch 才会 reject:DNS 失败、没有网络、被 CORS 拦截。
这意味着一个天真的 fetch 会乐呵呵地把错误页面交给你,然后在之后的 .json() 处崩溃:
解决办法是自己检查 response.ok,在服务器返回错误状态码时抛出异常:
要习惯写这个 if (!response.ok) 代码块。你写的每个 fetch 封装函数里都应该有它。
发送 POST 请求
默认是 GET。其他请求方法需要传入第二个参数,也就是一个选项对象:
有三点值得注意:
method默认是"GET"。POST、PUT、DELETE、PATCH 需要显式设置。body接收字符串(或FormData、Blob等),fetch 不会替你序列化对象,JSON.stringify(...)得你自己来。Content-Type请求头告诉服务器如何解析请求体。忘了设置的话,大多数服务器会把请求体当作纯文本处理。
请求头、查询字符串和其他选项
请求头就是一个对象(或一个 Headers 实例)。查询字符串需要你自己拼,通常用 URLSearchParams:
URLSearchParams 会替你处理编码,包括空格、& 符号和 Unicode 字符,这样输入中有需要转义的字符时,URL 也不会出错。
实际代码中你还会看到这些选项:credentials: "include" 用于跨域发送 cookie,cache: "no-store" 用于绕过 HTTP 缓存,mode: "cors"(通常是默认值)用于控制 CORS 行为。
用 AbortController 取消请求
有时你想放弃一个请求:用户输入了新的搜索词,或者请求耗时太久。AbortController 就是为此而生的机制:
controller.abort() 会让 fetch 的 promise 以一个 DOMException reject,它的 name 是 "AbortError"。finally 代码块会清除定时器,这样请求成功时也不会留下悬空的定时器。
这种 fetch 加超时再加清理的模式,值得封装成一个辅助函数,到处复用。
可复用的封装函数
把以上内容组合起来,就得到一个一次性处理样板代码的小工具:
一个地方修改请求头,一个地方处理错误,一个地方应对空响应。每个稍有规模的应用最后都会有类似的东西。
下一步:异步代码中的错误处理
fetch 是异步错误最常出现的地方之一,而 response.ok 检查只是其中一环。下一页讲解 promise 和 async/await 中的错误处理:错误去了哪里、如何捕获它们,以及哪些陷阱会让错误悄无声息地溜走。
常见问题
JavaScript 中 fetch 怎么用?
用目标 URL 调用 fetch(url)。它返回一个 Promise,resolve 的值是一个 Response 对象。再调用 response.json()(它同样返回 promise)来解析响应体。用 async/await 写就是:const res = await fetch(url); const data = await res.json();。
如何用 fetch 发送 POST 请求?
传入第二个参数,其中包含 method: 'POST'、一个 headers 对象(通常是 'Content-Type': 'application/json')和 body,对象要用 JSON.stringify(...) 转成字符串。fetch 不会替你序列化请求体。
为什么 fetch 遇到 404 或 500 时不会 reject?
fetch 只在网络失败时 reject:DNS 错误、没有连接、被 CORS 拦截。对 promise 来说,HTTP 错误状态码依然是成功的响应。你需要自己检查 response.ok(状态码在 200 至 299 时为 true)或 response.status,在服务器返回错误时抛出异常。
可以取消 fetch 请求吗?
可以,用 AbortController。创建一个实例,通过选项对象把它的 signal 传给 fetch,想取消时调用 controller.abort()。fetch 返回的 promise 会以 AbortError reject,你可以在 catch 中处理它。