12k
All articles

在 Node.js 中从 API 获取数据

使用 Node.js 原生 fetch 处理 API 请求、POST、超时和错误。并了解何时改用 undici Pool 或 Axios。

OpenReplay Team
OpenReplay Team
在 Node.js 中从 API 获取数据

如果你是一名编写 Node.js 代码的前端开发者,你可能出于习惯会使用 Axios,或者疑惑是否还需要 node-fetch。如今的答案比你想象的要简单:现代 Node.js 拥有稳定的内置 fetch API,对于大多数使用场景,它就是你所需要的全部。

以下是你需要了解的内容,以便在今天编写简洁、可靠的服务端 HTTP 请求。

核心要点

  • 现代 Node.js 内置了由 undici 驱动的全局 fetch API —— 无需安装任何包。
  • 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.okresponse.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

对于更复杂的场景 —— 比如你想基于用户操作取消请求同时强制执行超时 —— 你可以将 AbortControllerAbortSignal.any() 结合使用:

const controller = new AbortController()

const response = await fetch("https://api.example.com/data", {
  signal: AbortSignal.any([
    controller.signal,
    AbortSignal.timeout(5000),
  ]),
})

// 在其他地方调用 controller.abort() 来手动取消

何时直接使用 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:应该使用哪个?

原生 FetchAxios
需要安装
浏览器兼容
自动 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 的高级功能,将其保留在你的工具箱中 —— 但不要默认安装它。

常见问题

Node.js 中的内置 fetch API 稳定吗?

是的。全局 fetch API 在 Node.js 18 中作为实验性功能引入,并在 Node.js 21 中变得稳定。它由 undici 驱动,不需要额外的包。任何当前受支持的 Node.js 版本都开箱即用地包含它。

为什么 fetch 不会在 404 或 500 响应时抛出错误?

根据设计,fetch 只在网络级别故障(如 DNS 解析错误或连接丢失)时拒绝 promise。像 404 或 500 这样的 HTTP 错误状态码被视为有效响应。在处理响应体之前,你必须自己检查 response.ok 或 response.status。

我可以在同一个项目中同时使用 Axios 和原生 fetch 吗?

可以,没有冲突。一些团队对简单请求使用原生 fetch,在需要拦截器或自动错误抛出的地方使用 Axios。也就是说,混合使用 HTTP 客户端会增加复杂性,所以选择一个作为默认,只在需要其特定功能时使用另一个。

什么时候应该直接使用 undici 而不是全局 fetch?

当你需要连接池、对 HTTP 连接的精细控制,或对同一源的重复请求获得最大吞吐量时,直接使用 undici。对于简单性比原始性能更重要的典型 API 调用,全局 fetch 就足够了。

Open-source session replay

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.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.