- 后端
【免费下载链接】node-fetch
A light-weight module that brings the Fetch API to Node.js
node-fetch v3.x 是向 WHATWG Fetch Standard 全面看齐的一次重大重构:它提升最低 Node.js 版本、改为纯 ES Module 分发、移除timeout选项与非标准 API,同时带来data:URI、暴露 Blob、更好的 UTF-8 URL 处理等新能力。本文以官方升级指南(docs/v3-UPGRADE-GUIDE.md)为主线,结合本仓库的源码实现与测试用例,逐条拆解每项破坏性变更的动机、影响范围与迁移方案,帮助你快速完成从 v2 到 v3 的平滑过渡。
升级总览
v3.x 的改动方向非常明确:尽可能贴合 Fetch Standard,删掉一切非标准行为与私有 API。官方升级指南强调,本文档并非全部变更的穷举清单,而是最重要的破坏性变更集合;其他相对次要的改动请查阅项目发布说明与 v2 升级指南(其中记录的Headers规范化、.text()编码行为等改动在 v3 中继续延续)。在动手升级前,建议先对代码库做一次"破坏性 API 扫描",重点检查以下关键词:
timeout选项、textConverted()、require('node-fetch')new Request(相对路径)、new Response(相对路径)、res.body.on('error')res.json()的错误处理分支中对FetchError的判断
一、运行环境与模块系统的变化
1.1 最低 Node.js 版本提升至 12.20
自 2020 年 5 月起 Node.js 10 已进入 EOL 周期,v3 据此彻底放弃了对 Node.js 4、6、8、10 的支持(这些版本在 v2 中仍可运行)。当前仓库的 package.json 明确声明了引擎范围:
"engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }升级到 12.20 以上版本不仅是满足 node-fetch 的硬性要求,也让 Node.js 原生提供AbortController(14.17+)、完整 WHATWG URL API 等 v3 依赖的能力。如果你仍停留在旧版本,应优先升级运行时本身,否则 v3 无法安装或启动。
1.2 ESM-only:不再支持require()
v3 从3.0.0-beta.10起被转换为纯 ES Module 包。仓库的 package.json 中"type": "module"字段即是证据。这意味着:
// 以下写法在 v3 中会直接报错 const fetch = require('node-fetch'); // ERR_REQUIRE_ESM: Must use import to load ES Module迁移策略:如果项目本身就是 ESM,直接改用import:
import fetch from 'node-fetch';如果项目仍基于 CommonJS,官方给出两条出路:
继续使用 v2:v2 基于 CommonJS 构建,且官方承诺会继续为其发布关键 bug 修复。安装时锁定大版本即可:
npm install node-fetch@2用异步
import()从 CommonJS 加载 v3:// mod.cjs const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));注意:这种包装只解决了"能否拿到 fetch 函数"的问题,
Headers、Request、Response等类同样只能通过异步import()获取,异步上下文会带来额外的调用成本与类型系统复杂度,建议仅在无法整体迁移 ESM 时使用。
1.3 从 CommonJS 迁移的配套检查
ESM-only 还会连带影响你的测试框架、打包工具与类型配置:
- TypeScript:若项目使用
module: commonjs,即使装了 v3 也无法同步导入,需将模块目标升级为node16/nodenext或esnext(详见后文"捆绑 TypeScript 类型"一节)。 - 测试工具:本仓库自身的测试(如 test/main.js)全部使用
import语法;迁移时需检查现有require('node-fetch')出现在哪些模块,并逐一处理。
二、请求控制与超时机制的变革
2.1timeout选项被移除
timeout从未属于 Fetch Standard,v3 将其彻底删除。原因是规范化的AbortSignal能提供更细粒度的请求超时控制,且已是 Fetch 规范的一部分。若你的代码写过fetch(url, {timeout: 5000}),升级后该选项会被静默忽略——请求将不再按时限中断,这是最隐蔽的线上隐患之一。
2.2 用 AbortSignal 实现超时
官方推荐借助第三方timeout-signal包快速迁移,代码形态如下:
import timeoutSignal from 'timeout-signal'; import fetch from 'node-fetch'; const {AbortError} = fetch; fetch('https://www.google.com', {signal: timeoutSignal(5000)}) .then(response => { // 正常处理响应 }) .catch(error => { if (error instanceof AbortError) { // 处理超时 } });也可以不引入任何依赖,直接使用 Node.js 原生AbortController(Node 14.17+ 全局可用)手动实现,来自 README.md 的官方示例:
import fetch, {AbortError} from 'node-fetch'; const AbortController = globalThis.AbortController || await import('abort-controller'); const controller = new AbortController(); const timeout = setTimeout(() => { controller.abort(); }, 150); try { const response = await fetch('https://example.com', {signal: controller.signal}); const data = await response.json(); } catch (error) { if (error instanceof AbortError) { console.log('request was aborted'); } } finally { clearTimeout(timeout); }实现层面的印证:从 src/index.js 可以看到 v3 对signal的完整处理链路——fetch内部监听abort事件,触发abort()函数:reject 一个AbortError(定义于 src/errors/abort-error.js),销毁请求体流,并在响应体上emit('error', error)。若请求尚未完成,abort监听还会被finalize()及时移除,避免事件泄漏。在 src/request.js 中,signal必须是AbortSignal或EventTarget实例,否则抛出TypeError。
2.3req.body不再接受字符串
v3 正朝着"body 要么为 null、要么为流"的方向演进,因此请求体不再是字符串。仓库 body.js 的构造函数展示了 v3 实际接受的 body 类型:null、URLSearchParams、Blob、Buffer、ArrayBuffer、ArrayBufferView、Stream、FormData,以及其他可被String()强转的值。
迁移时请将字符串 body 改写为规范形态,例如使用URLSearchParams或Buffer.from(str):
// v2 时代的写法在 v3 中已不受推荐 await fetch(url, {method: 'POST', body: 'key=value'}); // 推荐改用 URLSearchParams,Content-Type 自动设为 // application/x-www-form-urlencoded;charset=UTF-8 const params = new URLSearchParams({key: 'value'}); await fetch(url, {method: 'POST', body: params});对应地,src/body.js 的extractContentType()会为URLSearchParams自动生成application/x-www-form-urlencoded;charset=UTF-8,为字符串生成text/plain;charset=UTF-8。
2.4 任意 URL 不再支持
v3 全面改用 WHATWG 的new URL()解析输入(src/request.js 中parsedURL = new URL(input)),因此缺少 base 的"任意 URL 字符串"将解析失败。例如:
// v2 中可运行的写法,v3 会抛 TypeError: Invalid URL await fetch('//example.com/path'); await fetch('/relative/path');这要求调用方始终提供完整、合法的绝对 URL(http:、https:、data:),详见下文"相对 URL 创建 Request/Response 不再支持"。
三、Response 行为的变化
3.1statusText不再自动派生默认值
v2 时代,如果服务端没有返回状态文本,node-fetch 会根据 HTTP 状态码自动补一个默认消息(如 404 → "Not Found")。这一行为不符合 Fetch Standard,v3 中statusText保持空白字符串。
仓库 src/response.js 中构造逻辑直接写为statusText: options.statusText || '',不再有任何派生逻辑。测试 test/main.js 也专门验证了这一点:
it('should handle response with no status text', async () => { const url = `${base}no-status-text`; const res = await fetch(url); expect(res.statusText).to.equal(''); // 期望为空字符串 await res.arrayBuffer(); });迁移影响:任何依赖res.statusText === 'OK'或根据statusText判断语义的代码都需要改为依赖res.status(或res.ok,见 src/response.js 中ok的定义:status >= 200 && status < 300)。
3.2res.textConverted()被移除
textConverted()是 v2 为保留 v1 编码探测行为而提供的非标准方法,v3 将其删除。需要字符集检测时,官方推荐改用第三方fetch-charset-detection包:
import fetch from 'node-fetch'; import convertBody from 'fetch-charset-detection'; fetch('https://somewebsite.com').then(async res => { const buf = await res.arrayBuffer(); const text = convertBody(buf, res.headers); });背后的行为变迁:v2 起.text()已固定按 UTF-8 解码(这是 Fetch Standard 的要求,详见 docs/v2-UPGRADE-GUIDE.md 中 ".text()no longer tries to detect encoding" 一节),v3 延续此行为——src/body.js 中text()使用new TextDecoder().decode(buffer),即始终 UTF-8。仓库还保留了 test/external-encoding.js 用于外部编码场景的测试,但 v3 自身不再提供自动探测。
3.3res.json()解析失败时抛出SyntaxError
v3 中,当res.json()遇到非法 JSON 时,抛出的是SyntaxError而非 v2 的FetchError,以对齐规范。这直接源于 src/body.js 的实现:
async json() { const text = await this.text(); return JSON.parse(text); // JSON.parse 失败即抛 SyntaxError }测试 test/main.js 验证了对非法 JSON 的拒绝行为:
it('should reject invalid json response', async () => { const url = `${base}error/json`; const res = await fetch(url); expect(res.headers.get('content-type')).to.equal('application/json'); return expect(res.json()).to.eventually.be.rejectedWith(Error); });迁移影响:如果你的错误处理逻辑依赖error instanceof FetchError来捕获 JSON 解析失败,必须增加对SyntaxError的判断分支;同时注意区分"HTTP 层错误(FetchError)"与"解析层错误(SyntaxError)"两种语义。
3.4 流错误转发与on('error')监听
v3 使用 Node.js 的stream pipeline转发请求/响应错误(详见后文"增强"部分)。这对消费响应体的方式有一个直接影响:错误可能被触发两次。在 Node.js ≥ 13.5 环境下,如果你用res.body.on('error', () => ...)监听错误,回调可能被调用两次;官方建议改为res.body.once('error', () => ...):
// v2 的写法 res.body.on('error', () => handleBodyError()); // v3 的推荐写法 res.body.once('error', () => handleBodyError());从 src/index.js 可以看到,响应体body正是通过pump(response_, new PassThrough(), ...)(pump即stream.pipeline的别名)构建的,pipeline 会把错误回调传递给PassThrough流,因此同一错误存在多次传播的路径。
四、包结构与导出的变化
4.1browser字段被移除
v2 的 package.json 中包含browser字段,用于服务端/浏览器双端场景;v3 明确 node-fetch只面向服务端,移除了该字段。若你在浏览器端使用 node-fetch,官方建议切换为cross-fetch这类浏览器兼容实现,而不是继续依赖 node-fetch 的浏览器入口。
4.2 默认 User-Agent 变更
默认 User-Agent 从node-fetch/1.0 (+https://github.com/node-fetch/node-fetch)改为:
node-fetch (+https://github.com/node-fetch/node-fetch)这个字符串不再带版本号。若你的服务端依赖 User-Agent 做版本识别或限流白名单,需要同步更新匹配规则。实现位于 src/request.js:
if (!headers.has('User-Agent')) { headers.set('User-Agent', 'node-fetch'); }注意:仅在请求方未显式传入User-Agent头时才使用该默认值,因此你可以通过headers选项覆盖它。
4.3 捆绑 TypeScript 类型
v3 起不再需要安装@types/node-fetch,类型定义直接随包分发。package.json 中"types": "./@types/index.d.ts"指向仓库内置的 @types/index.d.ts。该声明文件完整覆盖:
fetch()主函数签名:fetch(url: URL | RequestInfo, init?: RequestInit): Promise<Response>Headers、Request、Response、FetchError、AbortError等类RequestInit中 node-fetch 特有的扩展选项(agent、compress、follow、counter、size、highWaterMark、insecureHTTPParser等)- 从
fetch-blob重新导出的Blob、File、blobFrom、fileFrom等类型
类型正确性还由 @types/index.test-d.ts 配合tsd测试保障(npm run test-types)。迁移时请卸载@types/node-fetch,避免两套类型定义冲突。
五、v3 带来的能力增强
5.1data:URI 支持
v2 只支持http:协议;Fetch Standard 引入data:URI 后,v3 按规范实现了它。源码入口在 src/index.js 与 src/index.js:
const supportedSchemas = new Set(['data:', 'http:', 'https:']); // ... if (parsedURL.protocol === 'data:') { const data = dataUriToBuffer(request.url); const response = new Response(data, {headers: {'Content-Type': data.typeFull}}); resolve(response); return; }即data:URI 会通过data-uri-to-buffer解码为 Buffer,并自动带上解析出的 Content-Type。test/external-encoding.js 提供了丰富用例,覆盖 base64 图片、指定 charset、纯文本以及非法 data URI 的拒绝:
// base64 编码的 GIF const b64 = 'data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs='; const res = await fetch(b64); // res.headers.get('Content-Type') === 'image/gif' // 纯文本 data URI await fetch('data:,Hello%20World!'); // Content-Type 为 text/plain;charset=US-ASCII,text() 返回 'Hello World!'5.2 新暴露的 Blob 实现
v2 中Blob类型只在内部使用、不对外导出;v3 起 Blob 实现迁移到fetch-blob包,并成为公开 API。src/index.js 从fetch-blob/from.js导入并同时导出:
export {FormData, Headers, Request, Response, FetchError, AbortError, isRedirect}; export {Blob, File, fileFromSync, fileFrom, blobFromSync, blobFrom};这意味着你可以直接构造 Blob 作为请求体或读取响应为 Blob:
import fetch, {Blob} from 'node-fetch'; // 以 Blob 作为请求体 await fetch(url, {method: 'POST', body: new Blob(['a=1'])}); // 读取响应为 Blob const blob = await (await fetch(url)).blob(); const text = await blob.text();测试 test/main.js 验证了 Blob 的text()、arrayBuffer()、stream()读取以及"fetch → blob → 回传"的往返流程。
5.3 更好的 UTF-8 URL 处理
v3 使用 Node.js 内置的WHATWG-compliant URL API解析 URL(src/request.js 中parsedURL = new URL(input)),因此非 ASCII 的 UTF-8 URL 能被正确处理和编码,不再出现 v2 时代的乱码问题。
5.4 使用stream.pipeline转发错误
由于 v3 最低要求 Node.js 12.20,可以放心使用stream.pipeline这一新版 API。请求错误(如 DNS 失败、连接被拒)经由 pipeline 转发到响应体流,调用方能在res.body上收到统一的错误事件。仓库中的使用点包括 src/index.js(构建响应体)以及 src/index.js(gzip 解压管道)。这也是前文"错误可能触发两次"警告的根源,务必用once('error')消费。
5.5 相对 URL 创建 Request/Response 不再支持
与"任意 URL 不再支持"同源:v3 引入new URL()后,由于 Node.js 缺乏浏览器那样的 browsing context(且当时的 WHATWG URL API 存在限制),无法在无 base 的情况下解析相对 URL,因此new Request('/path')、new Response('/path')会直接抛错。fetch()主函数只接受绝对 URL 与data:URL。
迁移时要特别留意以相对路径拼接请求的代码,统一改为:
// 先解析为绝对 URL const url = new URL('/api/data', 'https://example.com').toString(); await fetch(url);六、升级自查清单
将上述变更浓缩为一份可执行的迁移检查表,供升级过程中逐项核对:
| 变更项 | v2 行为 | v3 行为 | 迁移动作 |
|---|---|---|---|
| Node.js 版本 | 支持 4/6/8/10 | 最低 12.20(package.json) | 升级运行时 |
| 模块格式 | CommonJS | ESM-only(package.json) | 改用import或异步import(),必要时锁定node-fetch@2 |
timeout选项 | 可用 | 移除 | 改用AbortSignal(timeout-signal或原生AbortController) |
req.body字符串 | 可用 | 不再接受 | 改用URLSearchParams/Buffer/Blob/流 |
statusText | 自动派生默认值 | 保持空白字符串(src/response.js) | 改用res.status/res.ok判断 |
res.textConverted() | 存在 | 移除 | 用fetch-charset-detection手动转换 |
res.json()错误 | FetchError | SyntaxError(src/body.js) | 错误处理增加SyntaxError分支 |
res.body.on('error') | 单次触发 | 可能触发两次 | 改用once('error') |
browser字段 | 存在 | 移除 | 浏览器场景切换cross-fetch |
| 默认 User-Agent | 带版本号 | node-fetch (+...)(src/request.js) | 更新 UA 匹配规则 |
@types/node-fetch | 需额外安装 | 类型随包内置(@types/index.d.ts) | 卸载外部类型包 |
data:URI | 不支持 | 支持(src/index.js) | 可直接使用 |
Blob | 内部类型 | 公开导出(src/index.js) | 可直接导入 |
| 相对 URL | 可用 | 抛错(src/request.js) | 统一转为绝对 URL |
最后两点建议:升级时先在测试环境完整跑一遍(本仓库通过npm test运行 mocha 测试套件),重点回归超时、编码与错误处理路径;若团队尚未准备好迁移 ESM,官方明确承诺 v2 会持续获得关键 bug 修复,可以在锁定node-fetch@2的同时规划渐进式迁移。
- 后端
【免费下载链接】node-fetch
A light-weight module that brings the Fetch API to Node.js
相关推荐
openai-node 迁移指南:全面解析从 node-fetch 到内置 Web fetch 的破坏性变更
openai node 迁移指南:全面解析从 node fetch 到内置 Web fetch 的破坏性变更 本文基于 MIGRATION.md https:/
AI 应用大模型后端React Query v3 迁移指南:从 v2 升级的破坏性变更与新增能力全解析
React Query v3 迁移指南:从 v2 升级的破坏性变更与新增能力全解析 React Query 在 v2 时代引入了大量新特性与"魔法",也因此积累
前端缓存状态管理Node-fetch v2到v3迁移终极指南:避坑技巧与新特性全解析
Node fetch v2到v3迁移终极指南:避坑技巧与新特性全解析 🚀 如果你正在使用Node.js进行网络请求,那么node fetch这个轻量级模块很可
后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考