fetch()返回Response的承诺,读取正文需要一秒钟await。- 404 和 500 等 HTTP 错误 不会拒绝 ,因此请务必检查
response.ok。 - 要发送 JSON,请使用
JSON.stringify并设置Content-Type标头。 - Fetch 没有默认超时选项,因此使用
AbortSignal.timeout()。
大多数现代网络应用程序始终与服务器通信。 JavaScript Fetch API 是执行此操作的标准方法,它内置于浏览器和 Node.js 18 及更高版本中,因此无需安装任何内容。
基本使用需要两行,但许多教程就到此为止并忽略了导致真正错误的部分:故障处理。我们针对 Node.js 24 上的小型本地服务器测试了本指南中的示例,显示的输出来自该运行。代码中的 example.com 代表您自己的 API。如果 Promise 对您来说是新的,请从 JavaScript Promise 解释 开始。
什么是获取 API?
Fetch 是一个基于 Promise 的接口,用于发出 HTTP 请求。它取代了旧的、回调驱动的XMLHttpRequest,现在许多项目都使用它,而不是添加一个库来进行简单的调用。
| 特点 | 获取 | Axios(图书馆) |
|---|---|---|
| 安装 | 内置 | 添加为依赖项 |
| JSON 正文 | 自己串起来 | 自动 |
| 解析 JSON 响应 | 致电 response.json() |
自动 |
| HTTP 错误拒绝? | 否 | 是的,默认情况下 |
| 超时 | 通过AbortSignal |
timeout 选项 |
GET 请求
Fetch 默认发出 GET 请求。有两个步骤,每个步骤都需要 await:首先响应标头到达,然后读取正文。
const response = await fetch("https://example.com/users/1");
const user = await response.json();
console.log(user);
// { id: 1, name: 'Asha' }
存在这两个步骤是因为正文可能很大并且比标头晚到达。在浏览器脚本中,顶级 await 需要一个模块,因此您通常会将其放入 async 函数中。请参阅我们的 JavaScript 异步/等待指南。
响应对象
在阅读正文之前,您可以检查响应:
console.log(response.status, response.ok, response.headers.get("content-type"));
// 200 true application/json
| 属性或方法 | 给你 |
|---|---|
response.ok |
true 适用于状态 200 至 299 |
response.status |
状态码,例如404 |
response.headers.get(name) |
1个响应头 |
response.json() |
正文解析为 JSON |
response.text() |
身体就像一根绳子 |
response.blob() |
二进制数据,例如图像 |
正文只能读取一次。在我们的测试中第二次调用 json() 失败:
await res.json();
await res.json();
// TypeError: Body is unusable: Body has already been read
如果您确实需要阅读两遍,请先调用response.clone()。
最大的惊喜:404 不会抛出异常
这是本文中最重要的事情。我们请求一个不存在的用户:
const res = await fetch("https://example.com/users/999");
console.log(res.status, res.ok);
// 404 false
没有抛出异常。就 fetch 而言,请求有效:已到达服务器并做出响应。仅当根本没有可用的响应到达时(例如网络故障),承诺才会拒绝。所以这样的代码有一个隐藏的错误:
try {
const res = await fetch(url);
const data = await res.json(); // may be an error payload
showUser(data); // runs anyway for 404 and 500
} catch (error) {
showError(); // does not run for 404 or 500
}
查看我们的 HTTP 状态代码备忘单 以了解每个代码的含义。
可重复使用的帮手
不要到处重复该检查。缠绕一次:
async function getJson(url, options) {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`Request failed with status ${response.status}`);
}
return response.json();
}
try {
await getJson("https://example.com/users/999");
} catch (error) {
console.log("caught:", error.message);
}
// caught: Request failed with status 404
现在 HTTP 错误和网络错误都以相同的 catch 结束。这个小功能值得每个项目都拥有。真实版本还可能从响应正文中读取错误消息,并处理 204 No Content 响应,该响应没有要解析的 JSON。
POST JSON 数据
传递选项对象作为第二个参数。对于 JSON,您必须自己做两件事:将数据转换为字符串并设置标头。
const created = await getJson("https://example.com/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Ben" }),
});
console.log(created);
// { id: 2, name: 'Ben', type: 'application/json' }
我们的测试服务器回显收到的内容类型,确认标头已到达。忘记该标头是导致服务器错误的常见原因,因为许多框架仅在声明 JSON 时才对其进行解析。要上传表单或文件,请传递 FormData 对象作为正文,并不设置 Content-Type,因为浏览器需要自行添加边界值。
其他方法和选项
| 选项 | 示例 | 目的 |
|---|---|---|
方法 |
"PUT"、"PATCH"、"DELETE" |
HTTP 方法 |
标头 |
{ 授权:“持有者...” } |
请求标头 |
身体 |
JSON.stringify(data) |
请求正文 |
信号 |
AbortSignal.timeout(5000) |
取消 |
凭证 |
“包括” |
跨域发送cookie |
缓存 |
“无商店” |
浏览器缓存行为 |
在 REST 和其他 API 风格之间进行选择?我们对 REST 与 GraphQL 的比较显示了 fetch 如何适合两者。
查询字符串
不要通过连接文本来构建查询字符串,因为特殊字符会破坏它们。 URLSearchParams 为您处理编码:
const params = new URLSearchParams({ q: "red shoes", page: 2 });
console.log(params.toString());
// q=red+shoes&page=2
await getJson(`https://example.com/search?${params}`);
// { q: 'red shoes', page: '2' }
服务器正确接收red shoes,空间恢复。
超时和取消
除非您另有说明,否则 Fetch 将在速度较慢的服务器上等待很长时间。 AbortSignal.timeout()设定了限制。我们的测试端点花费了两秒,而限制为 200 毫秒:
try {
await fetch("https://example.com/slow", { signal: AbortSignal.timeout(200) });
} catch (error) {
console.log(error.name);
}
// TimeoutError
要手动取消,例如当用户离开页面或键入新的搜索词时,请使用 AbortController:
const controller = new AbortController();
const pending = fetch("https://example.com/slow", { signal: controller.signal });
controller.abort();
try {
await pending;
} catch (error) {
console.log(error.name);
}
// AbortError
要一次运行多个请求,请使用 Promise.all,这比一次等待一个请求要快得多:
const [user, search] = await Promise.all([
getJson("https://example.com/users/1"),
getJson("https://example.com/search?q=a&page=1"),
]);
每一种失败
以下是我们运行中每次失败的情况,以及如何区分它们:
| 情况 | 会发生什么 | 如何检测 |
|---|---|---|
| 服务器返回404或500 | 承诺兑现 | response.ok 是错误的 |
| 无法访问服务器 | 拒绝TypeError |
抓住 |
| 已达到超时时间 | 拒绝TimeoutError |
error.name |
| 手动取消 | 拒绝AbortError |
error.name |
| 正文不是有效的 JSON | json() 拒绝 SyntaxError |
抓住 |
最后一行在生产中很常见。返回 502 的代理通常会发送 HTML 错误页面。我们复制了这个,并且 res.json() 抛出了 SyntaxError。在解析之前检查 response.ok 意味着您报告真正的问题,而不是令人困惑的解析错误。
提示: 有目的地测试这些故障路径。浏览器开发工具可以模拟离线状态,测试工具可以模拟 500 响应。我们的使用 Postman 进行 API 测试 指南涵盖了检查 API 方面的内容。
CORS 和凭据
在浏览器中,从一个源到另一个源的请求受 CORS(跨源资源共享规则)的约束。如果您看到提及 Access-Control-Allow-Origin 的错误,则修复属于服务器,该服务器必须允许您的站点。在您的 JavaScript 中,故障仅显示为一般情况 TypeError,因此请查看浏览器控制台以了解实际原因。 Node.js 不强制实施 CORS,因此在脚本中有效的请求在浏览器中仍然可能会失败。
默认情况下,浏览器不会针对跨域请求发送 cookie。如果需要,请设置credentials: "include",并确保服务器允许。切勿将秘密 API 密钥放入浏览器 JavaScript 中,因为任何人都可以读取它们。将它们保留在您的服务器上。
常见错误
- 不检查
response.ok。错误响应被视为成功。 - 忘记第二个
await.response.json()也会返回一个承诺。 - 忘记
JSON.stringify。在正文发送[object Object]时传递对象。 - 将
Content-Type设置为FormData.这会破坏边界,因此请将其保留。 - 没有超时。 挂起的请求可能会使页面的微调器无限期地运行。
- 解析 JSON 以获得空响应。 204 没有正文,
json()将抛出异常。
最后的话
一旦您记住其主要规则,Fetch 就很简单:响应并不等于成功。将小助手复制到您的项目中,添加超时并测试您的失败路径。下一步,构建一个小页面,从 API 加载数据,显示清晰的错误消息,并让用户取消缓慢的搜索。
常见问题
fetch 会抛出 404 错误吗?
否。仅当未收到响应(例如网络故障)时,fetch 才会拒绝。对于HTTP错误,请自行检查response.ok或response.status。
如何使用 fetch 发送 JSON?
设置method为POST,添加application/json的Content-Type header,并通过JSON.stringify作为body传递数据。
为什么 fetch 需要两次等待?
一旦响应标头到达,第一个问题就会解决。第二个等待正文下载并被解析,这可能会在稍后发生。
如何设置获取超时?
传递信号:选项中的AbortSignal.timeout(5000)。如果花费的时间超过那么多毫秒,则该请求将被拒绝,并出现 TimeoutError。
Node.js 中可以使用 fetch 吗?
是的。 Fetch 在 Node.js 18 及更高版本中全局可用。旧版本需要一个包,但 CORS 规则仅适用于浏览器。
继续学习
需要测试、WordPress 或 SEO 方面的帮助?
我们帮助团队交付更好的软件并发展他们的网站。告诉我们您正在做什么,我们将建议明确的下一步。
更多指南 测试、人工智能和现代开发
喜欢这篇文章吗?选择一个主题并直接跳至我们最新的教程。
人工智能幻觉解释:语言模型为何会产生幻觉以及如何减少它
人工智能幻觉解释:它们是什么,为什么语言模型产生它们,常见类型,如何通过基础和提示来减少它们,以及我们进行的简单检查。
阅读文章 →DevOps环境变量解释:.env 文件、秘密和配置正确完成
使用我们运行的 Python 示例解释环境变量:读取和设置它们、为什么每个值都是字符串、.env 文件、进程继承、秘密和验证。
阅读文章 →软件开发API 速率限制解释:算法、标头和处理 429 错误
API 速率限制通过我们运行的模拟进行了解释:令牌桶、固定窗口、滑动窗口、429 响应、速率限制标头、带回退的重试和测试限制。
阅读文章 →测试自动化Python 中的模拟:unittest.mock、补丁以及导致测试谎言的错误
使用unittest.mock 在Python 中进行模拟,经过测试:模拟和return_value、side_effect、修补使用名称的位置、spec 和autospec、模拟日期以及常见错误。
阅读文章 →软件测试代码覆盖率解释:行覆盖率与分支覆盖率以及 100% 的真正含义
用真实的coverage.py运行来解释代码覆盖率:行、分支和条件覆盖率,阅读报告,为什么100%覆盖率仍然会错过错误,以及合理的目标。
阅读文章 →数据库与 SQLSQL 中的 DELETE、TRUNCATE 和 DROP:差异、回滚以及何时使用它们
DELETE、TRUNCATE 和 DROP 通过经过测试的 SQL 进行了解释:各自删除什么、回滚、触发器、身份重置、速度,以及 PostgreSQL、MySQL、SQL Server 和 SQLite 有何不同。
阅读文章 →数据库与 SQLSQL 视图解释:创建、使用和避免陷阱(附示例)
SQL 视图通过我们运行的查询进行解释:创建视图、实时结果、隐藏列、性能、可更新和物化视图、破坏的依赖关系和最佳实践。
阅读文章 →软件开发TypeScript 接口与类型:真正的差异,使用编译器进行测试
TypeScript 接口与类型,使用真实编译器进行检查:两者都可以做什么,只有一个可以做什么,声明合并,扩展与交集以及要遵循的简单规则。
阅读文章 →软件开发Python *args 和 **kwargs 解释:灵活的函数和清晰的示例
Python *args 和 **kwargs 用我们运行的代码进行了解释:打包和解包、参数顺序、仅关键字和仅位置参数、装饰器和常见错误。
阅读文章 →软件开发Python Lambda 函数:语法、实际用途以及何时不使用它们
Python lambda 函数用我们运行的代码进行了解释:语法、使用键排序、映射和过滤器、闭包、后期绑定陷阱,以及何时普通 def 是更好的选择。
阅读文章 →人工智能什么是人工智能代理?它们是如何工作的,只需运行一个简单的代理循环
什么是人工智能代理?关于代理循环、工具、内存和护栏的简单指南,以及我们运行的一个小型 Python 循环、真正的风险以及何时简单的聊天机器人是更好的选择。
阅读文章 →DevOps监控与可观察性:真正的区别,包括日志、指标和跟踪
监控与可观察性解释:已知和未知的故障、日志、指标和跟踪、检查事件演练、黄金信号、OpenTelemetry 和有用的警报。
阅读文章 →DevOpsDocker Volumes 与 Bind Mounts:以正确的方式保留容器数据
Docker 卷与绑定挂载解释:为什么容器数据消失、命名卷、绑定挂载、tmpfs、Compose 示例、备份、权限以及何时使用它们。
阅读文章 →测试自动化TDD 与 BDD:主要区别、以两种方式构建的一个功能以及何时使用每种方式
TDD 与 BDD 的解释是通过一种双向构建的折扣功能:使用我们运行的代码进行红绿重构、Given-When-Then 场景、工具、权衡以及何时使用每种方案。
阅读文章 →软件测试用户验收测试 (UAT):流程、清单和真实示例
用户验收测试解释:谁运行它、它与 QA 有何不同、七步流程、UAT 测试用例模板、清单、签核标准和常见错误。
阅读文章 →数据库与 SQLSQL NULL 值解释:IS NULL、COALESCE、NULLIF 和要避免的陷阱
通过我们运行的查询解释了 SQL NULL 值:三值逻辑、IS NULL、COALESCE、NULLIF,聚合、连接和排序如何处理 NULL,以及它悄悄引起的错误。
阅读文章 →数据库与 SQLSQL CASE WHEN 语句:语法、实例和常见错误
SQL CASE WHEN 解释了我们运行的查询:简单和搜索表单、条件顺序、SUM 透视、COUNT 技巧、自定义排序、更新和要避免的 NULL 陷阱。
阅读文章 →软件开发什么是智威汤逊?通过工作示例解释 JSON Web 令牌
什么是智威汤逊? JSON Web Tokens 解释:这三个部分,在我们运行的代码中构建和篡改的令牌,JWT 与会话,存储,刷新令牌和安全错误。
阅读文章 →软件开发JavaScript 事件循环解释:调用堆栈、微任务和计时器以及示例
JavaScript 事件循环用我们运行的代码进行了解释:调用堆栈、任务和微任务队列、为什么 setTimeout 0 不是立即执行、阻塞、饥饿和面试测验。
阅读文章 →软件开发Python 生成器和 Yield 解释:通过真实示例节省内存
Python 生成器和yield 用我们运行的代码进行了解释:它们如何暂停和恢复、测量内存节省、管道、无限序列、yield from、发送和常见陷阱。
阅读文章 →人工智能什么是嵌入?向量和余弦相似度简单解释
什么是嵌入?关于向量、余弦相似度和语义搜索的简单语言指南,其中包含我们运行的一个小示例、常见用途、选择模型和已知限制。
阅读文章 →DevOps蓝绿部署与金丝雀部署:差异、权衡以及如何选择
蓝绿部署与金丝雀部署解释:每种部署的工作原理、成本、风险和回滚、我们测试的稳定用户分桶、数据库迁移、滚动更新以及如何选择。
阅读文章 →测试自动化不稳定的测试:造成这些问题的原因以及如何彻底解决它们
我们运行的演示解释了不稳定的测试:主要原因、如何确认和诊断、正确的时间修复、共享状态和日期以及隔离过程。
阅读文章 →软件测试负载测试与压力测试:差异、指标和 k6 示例
负载测试与压力测试的解释:各自证明什么、峰值和浸泡测试、重要的指标、为什么平均值会误导、k6 个示例以及如何计划测试。
阅读文章 →软件测试测试用例与测试场景:主要区别、示例以及何时使用它们
测试用例与测试场景的解释:清晰的定义、并排的表格、从场景到案例的密码重置功能、模板以及何时使用每个功能。
阅读文章 →数据库与 SQLSQL 查询优化:读取执行计划并修复慢速查询(带计时)
对一百万行进行 SQL 查询优化测试:读取执行计划、添加正确的索引、修复不可控制的过滤器、使用复合索引和键集分页。
阅读文章 →数据库与 SQLSQL UNION 与 UNION ALL:差异、示例和性能测试
SQL UNION 与 UNION ALL 通过我们运行的查询进行了解释:重复、都遵循的规则、真实的数据错误、测量的性能、INTERSECT 和 EXCEPT 以及何时使用它们。
阅读文章 →软件开发JavaScript Fetch API 教程:GET、POST、错误处理和超时
针对本地服务器测试的 JavaScript Fetch API 教程:GET 和 POST、为什么不抛出 404、可重用的帮助程序、查询字符串、超时、CORS 和常见错误。
阅读文章 →软件开发Python 文件处理:读取、写入、CSV、JSON 和 pathlib 以及示例
使用我们运行的代码解释了 Python 文件处理:打开模式、使用、读取大文件、CSV、JSON、pathlib、二进制文件、安全写入、错误处理和常见错误。
阅读文章 →软件开发Git merge 与 rebase 的区别:示例及如何选择
用真实的 log 输出、对比表、rebase 黄金法则和安全的团队工作流,讲清 Git merge 与 rebase 的区别。
阅读文章 →软件开发Python 列表推导式:语法、示例与何时避免使用
Python 列表推导式详解:语法、过滤、if-else、嵌套循环、实测耗时,以及何时普通 for 循环更合适。
阅读文章 →数据库与 SQLSQL JOIN 详解:附示例(INNER、LEFT、RIGHT、FULL)
通过示例表和查询,一步步讲解 SQL JOIN:INNER、LEFT、RIGHT、FULL、CROSS 和 SELF,并介绍常见错误与性能技巧。
阅读文章 →