☰
undici 快速上手指南:从 fetch 到连接池、超时与错误处理
2026/9/28 2:43:20 网站建设 项目流程
  • 后端
  • 网络
  • 通信

【免费下载链接】undici

An HTTP/1.1 client, written from scratch for Node.js

项目地址:https://gitcode.com/gh_mirrors/un/undici
点击查看免费下载

undici 是一个为 Node.js 从零实现的 HTTP/1.1 客户端,同时也提供了符合 Web 标准的fetch、Request、Headers等 API。本文以 docs/docs/getting-started.md 为主线,结合仓库源码,系统讲解 undici 的安装、fetch用法、三种 Dispatcher(Agent/Pool/Client)的连接复用机制、超时配置、结构化错误处理,以及代理、Mock 测试、全局安装等常见实战模式。读完本文,你将掌握如何用 undici 写出高并发、连接可复用、超时与错误可控的生产级 HTTP 客户端代码。

安装

undici 通过 npm 安装,推荐在 Node.js 项目中作为依赖引入:

npm install undici

安装完成后即可在项目中使用 CommonJS 或 ESM 方式引入。注意:undici 自身也提供了 Node.js 内置fetch的替代实现(详见下文"自定义全局 fetch"一节),当你想完全掌控 HTTP 客户端的连接管理、超时与错误行为时,显式引入 undici 是更优选择。

Fetch:最快的起步方式

undici 导出的fetch严格遵循 Fetch 标准,其用法与浏览器 API 完全一致,是开始使用 undici 最快捷的方式:

import { fetch } from 'undici' const res = await fetch('https://example.com') const data = await res.json() console.log(data)

fetch返回标准的Response对象,res.json()、res.text()、res.arrayBuffer()、res.formData()等方法都可用。在仓库中,fetch实现在 lib/web/fetch/index.js,而request/stream/pipeline/upgrade/connect等底层 API 统一由 lib/api/index.js 导出。

使用 Request 对象

undici 同时导出了符合 Fetch 标准的Request类,可以显式构造请求再交给fetch:

import { fetch, Request } from 'undici' const req = new Request('https://example.com', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ hello: 'world' }) }) const res = await fetch(req) console.log(res.status)

Request支持method、headers、body、signal等标准选项,适合把"请求描述"与"请求发送"分离,便于在多个调用点复用同一个请求对象。

流式读取响应

res.body是一个 Web 标准的ReadableStream。可以用node:stream/promises的pipeline直接把响应体流式写入文件,对大文件下载非常友好(边下载边落盘,内存占用恒定):

import { fetch } from 'undici' import { pipeline } from 'node:stream/promises' import { createWriteStream } from 'node:fs' const res = await fetch('https://example.com/large-file.zip') await pipeline(res.body, createWriteStream('./file.zip'))

务必消费或取消响应体。Node.js 的垃圾回收并不足够激进,无法及时释放连接。如果响应体未被读取就丢弃,会导致连接泄漏、请求停滞。参见 Specification Compliance - Garbage Collection。这也是 undici 的Client提供body.dump()这类"丢弃并消费响应体"方法的原因。

更多fetch细节见 API Reference: Fetch。

Dispatcher:连接复用与连接池

默认情况下,fetch、request、stream、pipeline每次调用都会新建一条连接。对高频请求同一 origin 的应用来说,这是极大的浪费。undici 提供了dispatcher(调度器)来在内部管理连接。

dispatcher 的本质是"分发器":它接收请求参数(origin、path、method 等)与 handler,内部决定使用哪条连接来发送请求。从源码结构看,request等 API 最终都会调用getGlobalDispatcher()拿到当前 dispatcher 并执行dispatch(见 lib/api/index.js 与 lib/global.js)。

Agent—— 多 origin 场景的通用调度器

Agent是最通用的 dispatcher,它按 origin 分别维护连接池,是大多数应用的推荐默认选择。通过setGlobalDispatcher可以全局生效:

import { Agent, setGlobalDispatcher, fetch } from 'undici' const agent = new Agent({ keepAliveTimeout: 30_000, keepAliveMaxTimeout: 600_000 }) setGlobalDispatcher(agent) // 之后所有的 fetch/request/stream/pipeline 调用都会复用连接 const res = await fetch('https://api.example.com/data')

也可以按请求单独指定 dispatcher:

await fetch('https://api.example.com/data', { dispatcher: agent })

从 lib/dispatcher/agent.js 的实现可以看到,Agent内部维护kClients(Map)与kOrigins(Set):

  • 每个 origin 首次请求时通过factory惰性创建底层 dispatcher,之后同一 origin 的请求复用;
  • 默认工厂函数(lib/dispatcher/agent.js#L18-L22)会按connections选项决定创建Client(连接数 1)还是Pool(连接池);
  • maxOrigins选项限制可管理的 origin 数量,超出会抛出MaxOriginsReachedError;
  • allowH2: false时会对该 origin 使用独立的#http1-only连接池键,强制走 HTTP/1.1。

Pool—— 单一 origin 的固定连接池

Pool面向单一 origin,管理一组固定数量的连接,让你显式控制并发度:

import { Pool, request } from 'undici' const pool = new Pool('https://api.example.com', { connections: 10 }) const { body } = await request('https://api.example.com/data', { dispatcher: pool }) const data = await body.json() pool.close()

connections: 10表示最多维护 10 条并发连接,超出时请求进入内部队列等待空闲连接。用完记得调用pool.close()优雅关闭。

Client—— 单连接与管道化(pipelining)

Client对应单条 TCP 连接。它支持 pipelining——在响应返回之前就连续发送多个请求,显著提升吞吐:

import { Client } from 'undici' const client = new Client('https://api.example.com', { pipelining: 5 }) const { body } = await client.request({ path: '/', method: 'GET' }) await body.dump() client.close()

pipelining只应在可信的远端服务器上启用。HTTP/1.1 管道化要求服务器按顺序处理并响应请求,若服务器行为不规范,可能造成响应错乱。

Client还支持path、method等底层请求参数,返回的body是一个可流式读取的对象(支持json()、text()、dump()等方法)。从 lib/dispatcher/client.js 的实现可以看到,Client是 undici 连接管理的核心单元,Agent与Pool最终都建立在它之上。

更多 dispatcher 选项与生命周期管理,参见:

  • API Reference: Agent
  • API Reference: Pool
  • API Reference: Client

超时:两个层面,精确控制

undici 在两个层面施加超时:

  • headersTimeout—— 等待响应头到达的时间(默认300s)。
  • bodyTimeout—— 相邻两个响应体数据块之间的最大间隔时间(默认300s)。

两个选项既可配置在 dispatcher 上,也可按请求覆盖:

import { Agent, setGlobalDispatcher } from 'undici' const agent = new Agent({ headersTimeout: 5_000, bodyTimeout: 30_000 }) setGlobalDispatcher(agent)

这些默认值与参数校验都可以在源码中验证:见 lib/dispatcher/client.js#L193-L198(headersTimeout/bodyTimeout必须是非负整数)以及 lib/dispatcher/client.js#L307-L317(默认值300e3即 300 秒)。

此外还有两个连接层超时值得了解:

  • 连接超时(connect timeout):connect选项中的timeout默认10 秒(10e3),超时抛出ConnectTimeoutError,见 lib/core/connect.js#L68-L75。
  • keep-alive 超时:keepAliveTimeout默认4 秒,keepAliveMaxTimeout默认600 秒,keepAliveTimeoutThreshold默认 2 秒(lib/dispatcher/client.js#L307-L309)。空闲连接在该窗口内保持复用。

超时错误会以HeadersTimeoutError和BodyTimeoutError抛出。完整错误列表见 API Reference: Errors。

错误处理:通过error.code精确分支

undici 通过error.code暴露结构化错误,方便在catch中精确分支:

import { request, errors } from 'undici' try { const { body } = await request('https://example.com') await body.json() } catch (err) { switch (err.code) { case 'UND_ERR_CONNECT_TIMEOUT': console.error('Connection timed out') break case 'UND_ERR_HEADERS_TIMEOUT': console.error('Headers timed out') break case 'UND_ERR_BODY_TIMEOUT': console.error('Body timed out') break case 'UND_ERR_ABORTED': console.error('Request was aborted') break default: console.error(err) } }

错误码体系定义在 lib/core/errors.js:所有错误都继承自UndiciError(基类code === 'UND_ERR'),常见的子类与code对应关系如下:

code错误类触发场景
UND_ERRUndiciError所有 undici 错误的基类
UND_ERR_CONNECT_TIMEOUTConnectTimeoutError建立连接超时(默认 10s)
UND_ERR_HEADERS_TIMEOUTHeadersTimeoutError等待响应头超时(默认 300s)
UND_ERR_BODY_TIMEOUTBodyTimeoutError响应体块间隔超时(默认 300s)
UND_ERR_INVALID_ARGInvalidArgumentError参数非法(如负数超时)
UND_ERR_ABORTEDRequestAbortedError请求被 AbortController 中止
UND_ERR_HEADERS_OVERFLOWHeadersOverflowError响应头超出限制

各错误类通过Symbol.for('undici.error.UND_ERR_*')唯一标识(见 lib/core/errors.js#L20-L90),同时支持instanceof判断与code字符串判断两种方式。

中止请求(Abort)

undici 原生支持标准AbortController,这是浏览器生态兼容的关键设计:

import { request } from 'undici' const ac = new AbortController() setTimeout(() => ac.abort(), 1000) try { const { body } = await request('https://example.com', { signal: ac.signal }) await body.dump() } catch (err) { console.error(err.code) // UND_ERR_ABORTED }

传入signal: ac.signal后,调用ac.abort()会中止请求并抛出code === 'UND_ERR_ABORTED'的错误。注意:请求被中止时同样需要消费/取消响应体,避免连接残留。

常见实战模式

代理(Proxy)

通过ProxyAgent使用 HTTP(S) 代理,或用EnvHttpProxyAgent自动读取环境变量中的代理配置:

import { ProxyAgent, setGlobalDispatcher } from 'undici' const proxy = new ProxyAgent('http://proxy.internal:8080') setGlobalDispatcher(proxy)

EnvHttpProxyAgent会从HTTP_PROXY/HTTPS_PROXY/NO_PROXY等环境变量中读取代理设置,适合容器、CI 等场景。详见 Best Practices: Proxy 和 API Reference: ProxyAgent。

测试中的 Mock

undici 内置了完整的 Mock 体系,无需启动真实服务器即可模拟接口:

import { MockAgent, setGlobalDispatcher, request } from 'undici' const mockAgent = new MockAgent() setGlobalDispatcher(mockAgent) const mockPool = mockAgent.get('https://api.example.com') mockPool.intercept({ path: '/users' }).reply(200, [{ id: 1 }]) const { body } = await request('https://api.example.com/users') console.log(await body.json())

MockAgent支持按 path、method 精确拦截,并可在测试结束后断言所有 mock 是否都被命中。详见 Best Practices: Mocking Request 和 API Reference: MockAgent。

用 undici 写测试套件

在测试环境中,设置较短的 keep-alive 超时可以避免测试收尾时长时间等待连接释放:

import { Agent, setGlobalDispatcher } from 'undici' const agent = new Agent({ keepAliveTimeout: 10, keepAliveMaxTimeout: 10 }) setGlobalDispatcher(agent)

详见 Best Practices: Writing Tests。

自定义全局 fetch:install()

你可以用install()覆盖 Node.js 内置的全局对象,让全局的fetch、Headers、Response、Request、FormData等全部来自 undici 而非 Node.js 内置实现:

import { install } from 'undici' install() // 全局的 fetch, Headers, Response, Request, FormData // 现在来自 undici,而不是 Node.js 内置 bundle const res = await fetch('https://example.com')

从 lib/global.js#L61-L76 可以看到,install()实际会替换的全局对象包括fetch、Headers、Response、Request、FormData,以及WebSocket、CloseEvent、ErrorEvent、MessageEvent、EventSource。这让 undici 在 Node.js 中提供了一套完整、行为一致的 Web 平台 API。更多细节见 API Reference: Global Installation。

另外,setGlobalDispatcher的实现(lib/global.js#L18-L55)会把 dispatcher 挂载到globalThis上的版本化 Symbol(undici.globalDispatcher.2),当globalThis被冻结不可扩展时还会回退到内部存储,保证极端环境下依然可用。

进一步阅读

  • Undici vs. Built-in Fetch —— 何时安装 undici、何时使用 Node.js 内置 fetch 的取舍
  • API Reference: Dispatcher —— 完整的 dispatcher API 文档
  • Examples —— 可运行的代码示例(fetch.js、request.js、proxy.js、proxy-agent.js等)
  • API Reference: Errors —— 全部错误类型与code清单
  • 后端
  • 网络
  • 通信

【免费下载链接】undici

An HTTP/1.1 client, written from scratch for Node.js

项目地址:https://gitcode.com/gh_mirrors/un/undici
点击查看免费下载
上一篇:5分钟快速上手:用voxel-engine打造你的第一个JavaScript体素世界 🚀
下一篇:未来展望:repvit_m2_3.dist_450e_in1k在计算机视觉领域的应用前景与发展路线图

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询