在 Node.js 中从 API 获取数据
如果你是一名编写 Node.js 代码的前端开发者,你可能出于习惯会使用 Axios,或者疑惑是否还需要 node-fetch。如今的答案比你想象的要简单:现代 Node.js 拥有稳定的内置 fetch API,对于大多数使用场景,它就是你所需要的全部。
以下是你需要了解的内容,以便在今天编写简洁、可靠的服务端 HTTP 请求。
核心要点
- 现代 Node.js 内置了由 undici 驱动的全局
fetchAPI —— 无需安装任何包。 fetch不会在 HTTP 错误(如 404 或 500)时抛出异常。在读取响应体之前,始终检查response.ok。- 使用
AbortSignal.timeout()设置请求超时,而不是手动配置AbortController。 - 对于向同一源发起重复请求的高吞吐量场景,使用 undici 的
Pool来实现连接复用。 - 在仅用于 Node 的项目中,优先使用原生
fetch而非 Axios,以保持依赖列表的精简。
Node.js Fetch API 已稳定且内置
从 Node.js 18 开始,fetch 全局可用,无需导入。它在 v18 中被标记为实验性功能,然后在 v21 中稳定。它底层由 undici 驱动 —— undici 是支持 Node 的 fetch 实现的 HTTP 客户端 —— 并且它与你已经熟悉的浏览器 Fetch API 保持一致。
无需安装。无需 require('node-fetch')。只需 fetch()。
const response = await fetch("https://api.github.com/users/nodejs/repos", {
headers: { "User-Agent": "my-app" },
})
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`)
}
const repos = await response.json()
console.log(repos.map((r) => r.name))
需要理解的一个重要点:fetch 不会在 HTTP 错误(如 404 或 500)时抛出异常。它只在网络故障时抛出异常。在读取响应体之前,始终检查 response.ok 或 response.status。
使用 Node.js Fetch API 发起 POST 请求
发送数据很简单。设置 method,添加 Content-Type 头,并序列化你的负载:
const response = await fetch("https://api.example.com/posts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title: "Hello", body: "World" }),
})
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`)
}
const data = await response.json()
对于 FormData 上传,完全跳过 Content-Type 头 —— fetch 会自动设置正确的 multipart boundary。
使用 AbortSignal 处理超时
fetch 调用本身没有内置的超时选项,但 AbortSignal.timeout() 使其变得简洁:
const response = await fetch("https://api.example.com/data", {
signal: AbortSignal.timeout(5000), // 5 秒
})
如果请求超过限制时间,请求将被中止。对于简单场景,无需手动设置 AbortController。
对于更复杂的场景 —— 比如你想基于用户操作取消请求同时强制执行超时 —— 你可以将 AbortController 与 AbortSignal.any() 结合使用:
const controller = new AbortController()
const response = await fetch("https://api.example.com/data", {
signal: AbortSignal.any([
controller.signal,
AbortSignal.timeout(5000),
]),
})
// 在其他地方调用 controller.abort() 来手动取消
Discover how at OpenReplay.com.
何时直接使用 Undici
对于大多数 Node.js API 请求,全局 fetch 是正确的工具。但如果你要向同一源发起大量请求 —— 比如反复调用内部服务 —— undici 的 Pool 可以为你提供连接复用和更精细的控制。
import { Pool } from "undici"
const pool = new Pool("https://internal-api.example.com", {
connections: 10,
})
const { statusCode, body } = await pool.request({
path: "/data",
method: "GET",
})
const data = await body.json()
注意 pool.request() 返回的 body 是一个可读流,而不是 Response 对象。你需要显式地消费它 —— 例如使用 body.json() 或 body.text()。
当你需要高级连接池、高效流式传输大型响应或在性能敏感代码中获得最大吞吐量时,直接使用 undici。
Node.js Fetch vs Axios:应该使用哪个?
| 原生 Fetch | Axios | |
|---|---|---|
| 需要安装 | 否 | 是 |
| 浏览器兼容 | 是 | 是 |
| 自动 JSON 解析 | 否(调用 .json()) | 是 |
| 拦截器 | 手动 | 内置 |
| HTTP 错误时抛出异常 | 否 | 是 |
| 超时选项 | AbortSignal | 内置 |
如果你需要拦截器、自动 JSON 解析,或在同一代码库中跨浏览器和 Node 保持一致的行为,Axios 仍然是一个可靠的选择。但如果你编写的是仅用于 Node 的代码,并且不需要这些额外功能,原生 fetch 可以保持你的依赖列表简洁。
对于新项目,避免使用 node-fetch。它在 Node 18 之前是正确的解决方案,但在任何受支持的 Node.js 版本上,它现在都是多余的。
结论
对于现代 Node.js 中的服务端 HTTP 请求,从内置的 fetch 开始。它稳定、熟悉,并且不需要任何额外的东西。使用 AbortSignal.timeout() 处理超时,检查 response.ok 处理错误,只有在性能需要时才使用 undici 的 Pool。如果你确实需要 Axios 的高级功能,将其保留在你的工具箱中 —— 但不要默认安装它。
常见问题
是的。全局 fetch API 在 Node.js 18 中作为实验性功能引入,并在 Node.js 21 中变得稳定。它由 undici 驱动,不需要额外的包。任何当前受支持的 Node.js 版本都开箱即用地包含它。
根据设计,fetch 只在网络级别故障(如 DNS 解析错误或连接丢失)时拒绝 promise。像 404 或 500 这样的 HTTP 错误状态码被视为有效响应。在处理响应体之前,你必须自己检查 response.ok 或 response.status。
可以,没有冲突。一些团队对简单请求使用原生 fetch,在需要拦截器或自动错误抛出的地方使用 Axios。也就是说,混合使用 HTTP 客户端会增加复杂性,所以选择一个作为默认,只在需要其特定功能时使用另一个。
当你需要连接池、对 HTTP 连接的精细控制,或对同一源的重复请求获得最大吞吐量时,直接使用 undici。对于简单性比原始性能更重要的典型 API 调用,全局 fetch 就足够了。
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.