node-fetch:基于 Node.js 生态的 HTTP 客户端项目

可用于在 Node.js 环境中发起 HTTP 请求,实现与浏览器 fetch API 兼容的功能。支持原生 promise、异步函数及 Node 流,能自动解码内容编码,提供重定向限制、响应大小限制等实用扩展。【此简介由AI生成】

分支1Tags1
文件最后提交记录最后更新时间
2 年前
1 年前
1 年前
2 年前
2 年前
2 年前
2 年前
1 年前
2 年前
2 年前
2 年前
2 年前
2 年前
2 年前
2 年前
2 年前
1 年前
2 年前
2 年前
2 年前
Node Fetch

一款轻量级模块,将 Fetch API 引入 Node.js 环境。

构建状态 覆盖率状态 当前版本 安装体积 收录于 Awesome Node.js Discord

欢迎通过 Open Collective 支持我们:

Open Collective

您可能正在查看 v2 版本文档

初衷

与其在 Node.js 中实现 XMLHttpRequest 来运行浏览器专用的 Fetch 垫片,何不直接从原生 http 模块过渡到 fetch API?因此诞生了 node-fetch,它以最简代码在 Node.js 运行时实现了与 window.fetch 兼容的 API。

如需同构使用(服务端导出 node-fetch,客户端导出 whatwg-fetch),可参考 Jason Miller 的 isomorphic-unfetch 或 Leonardo Quixada 的 cross-fetch

特性

  • 严格遵循 window.fetch API 规范
  • 在实现 WHATWG fetch 标准流处理标准 时做出合理取舍,并记录已知差异
  • 基于原生 Promise 和异步函数实现
  • 请求与响应体均采用 Node 原生流处理
  • 自动解压内容编码(gzip/deflate/brotli),并将字符串输出(如 res.text()res.json())自动转换为 UTF-8 编码
  • 提供实用扩展功能:重定向限制、响应大小限制、明确的错误类型 便于问题排查

与客户端 fetch 的区别

  • 版本差异说明:
  • 若您需要使用 window.fetch 支持但本库缺失的功能,欢迎提交 issue
  • 我们也诚挚欢迎 Pull Request 贡献!

安装

当前稳定版(3.x)要求 Node.js 版本至少为 12.20.0。

npm install node-fetch

加载与配置模块

ES 模块 (ESM)

import fetch from 'node-fetch';

CommonJS

从 v3 版本开始,node-fetch 仅支持 ESM 模块规范——无法通过 require() 导入该模块。

若您暂时无法迁移至 ESM 规范,请继续使用兼容 CommonJS 的 v2 版本。针对 v2 版本的关键错误修复仍将持续发布。

npm install node-fetch@2

或者,你也可以使用 CommonJS 中的异步 import() 函数来动态加载 node-fetch

// mod.cjs
const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));

提供全局访问

要在不导入的情况下使用 fetch(),可以在 node 中修补 global 对象:

// fetch-polyfill.js
import fetch, {
  Blob,
  blobFrom,
  blobFromSync,
  File,
  fileFrom,
  fileFromSync,
  FormData,
  Headers,
  Request,
  Response,
} from 'node-fetch'

if (!globalThis.fetch) {
  globalThis.fetch = fetch
  globalThis.Headers = Headers
  globalThis.Request = Request
  globalThis.Response = Response
}

// index.js
import './fetch-polyfill'

// ...

升级指南

正在使用旧版 node-fetch?请查阅以下文件:

常见用法

注意:以下文档针对 3.x 版本保持最新,若您使用的是旧版,请查看如何升级

纯文本或 HTML 请求

import fetch from 'node-fetch';

const response = await fetch('https://github.com/');
const body = await response.text();

console.log(body);

JSON

import fetch from 'node-fetch';

const response = await fetch('https://api.github.com/users/github');
const data = await response.json();

console.log(data);

简单文章

import fetch from 'node-fetch';

const response = await fetch('https://httpbin.org/post', {method: 'POST', body: 'a=1'});
const data = await response.json();

console.log(data);

使用 JSON 发布

import fetch from 'node-fetch';

const body = {a: 1};

const response = await fetch('https://httpbin.org/post', {
	method: 'post',
	body: JSON.stringify(body),
	headers: {'Content-Type': 'application/json'}
});
const data = await response.json();

console.log(data);

带表单参数的 POST 请求

从 Node.js v10.0.0 开始,全局对象中提供了 URLSearchParams。更多使用方法请参阅官方文档

注意:只有当 URLSearchParams 实例直接作为参数传递时,Content-Type 头才会自动设置为 x-www-form-urlencoded

import fetch from 'node-fetch';

const params = new URLSearchParams();
params.append('a', 1);

const response = await fetch('https://httpbin.org/post', {method: 'POST', body: params});
const data = await response.json();

console.log(data);

异常处理

注意:3xx-5xx 响应属于异常,应在 then() 中处理,详见下一节。

将 fetch 函数包裹在 try/catch 代码块中,可以捕获所有异常,包括源自 Node 核心库的错误(如网络错误)以及属于 FetchError 实例的操作错误。更多细节请参阅错误处理文档

import fetch from 'node-fetch';

try {
	await fetch('https://domain.invalid/');
} catch (error) {
	console.log(error);
}

处理客户端与服务器错误

通常我们会创建一个辅助函数,用于检查响应是否包含客户端(4xx)或服务器(5xx)错误:

def check_response(response):
    """验证响应状态码是否属于错误范围"""
    if 400 <= response.status_code < 500:
        raise ClientError(f"客户端错误: {response.status_code}")
    elif 500 <= response.status_code < 600:
        raise ServerError(f"服务器错误: {response.status_code}")
    return response
import fetch from 'node-fetch';

class HTTPResponseError extends Error {
	constructor(response) {
		super(`HTTP Error Response: ${response.status} ${response.statusText}`);
		this.response = response;
	}
}

const checkStatus = response => {
	if (response.ok) {
		// response.status >= 200 && response.status < 300
		return response;
	} else {
		throw new HTTPResponseError(response);
	}
}

const response = await fetch('https://httpbin.org/status/400');

try {
	checkStatus(response);
} catch (error) {
	console.error(error);

	const errorBody = await error.response.text();
	console.error(`Error body: ${errorBody}`);
}

默认情况下,系统不会存储 Cookie。但通过操作请求头和响应头,可以提取并传递 Cookie。具体方法请参阅 提取 Set-Cookie 头信息

高级用法

流处理

"Node.js 风格"的核心在于尽可能使用流式操作。您可以将 res.body 通过管道传输到另一个流中。以下示例使用 stream.pipeline 来添加流错误处理程序,并等待下载完成。

import {createWriteStream} from 'node:fs';
import {pipeline} from 'node:stream';
import {promisify} from 'node:util'
import fetch from 'node-fetch';

const streamPipeline = promisify(pipeline);

const response = await fetch('https://github.githubassets.com/images/modules/logos_page/Octocat.png');

if (!response.ok) throw new Error(`unexpected response ${response.statusText}`);

await streamPipeline(response.body, createWriteStream('./octocat.png'));

在 Node.js 14 中,你也可以使用异步迭代器来读取 body;但要注意捕获错误——响应运行的时间越长,遇到错误的可能性就越大。

import fetch from 'node-fetch';

const response = await fetch('https://httpbin.org/stream/3');

try {
	for await (const chunk of response.body) {
		console.dir(JSON.parse(chunk.toString()));
	}
} catch (err) {
	console.error(err.stack);
}

在 Node.js 12 中,你也可以使用异步迭代器来读取 body;然而,流式异步迭代器直到 Node.js 14 才趋于成熟,因此你需要额外处理一些工作,以确保直接从流中捕获错误,并等待响应完全关闭。

import fetch from 'node-fetch';

const read = async body => {
	let error;
	body.on('error', err => {
		error = err;
	});

	for await (const chunk of body) {
		console.dir(JSON.parse(chunk.toString()));
	}

	return new Promise((resolve, reject) => {
		body.on('close', () => {
			error ? reject(error) : resolve();
		});
	});
};

try {
	const response = await fetch('https://httpbin.org/stream/3');
	await read(response.body);
} catch (err) {
	console.error(err.stack);
}

访问请求头与其他元数据

import fetch from 'node-fetch';

const response = await fetch('https://github.com/');

console.log(response.ok);
console.log(response.status);
console.log(response.statusText);
console.log(response.headers.raw());
console.log(response.headers.get('content-type'));

与浏览器不同,您可以通过 Headers.raw() 方法手动访问原始的 Set-Cookie 头部信息。这是 node-fetch 独有的 API。

import fetch from 'node-fetch';

const response = await fetch('https://example.com');

// Returns an array of values, instead of a string of comma-separated values
console.log(response.headers.raw()['set-cookie']);

使用文件提交数据

import fetch, {
  Blob,
  blobFrom,
  blobFromSync,
  File,
  fileFrom,
  fileFromSync,
} from 'node-fetch'

const mimetype = 'text/plain'
const blob = fileFromSync('./input.txt', mimetype)
const url = 'https://httpbin.org/post'

const response = await fetch(url, { method: 'POST', body: blob })
const data = await response.json()

console.log(data)

node-fetch 内置了符合规范的 FormData 实现,可用于发送 multipart/form-data 格式的请求负载。

import fetch, { FormData, File, fileFrom } from 'node-fetch'

const httpbin = 'https://httpbin.org/post'
const formData = new FormData()
const binary = new Uint8Array([ 97, 98, 99 ])
const abc = new File([binary], 'abc.txt', { type: 'text/plain' })

formData.set('greeting', 'Hello, world!')
formData.set('file-upload', abc, 'new name.txt')

const response = await fetch(httpbin, { method: 'POST', body: formData })
const data = await response.json()

console.log(data)

若因某些原因需要发布来自任意位置的流数据, 可附加一个类似 BlobFile 的对象。

最低要求是具备:

  1. 拥有 Symbol.toStringTag 的 getter 或属性,其值为 BlobFile
  2. 已知大小
  3. 提供 stream() 方法或 arrayBuffer() 方法,后者需返回 ArrayBuffer

其中 stream() 方法需返回任意异步可迭代对象,只要其生成 Uint8Array(或 Buffer)即可, 因此 Node.Readable 流和 whatwg 流均能完美兼容。

formData.append('upload', {
	[Symbol.toStringTag]: 'Blob',
	size: 3,
  *stream() {
    yield new Uint8Array([97, 98, 99])
	},
	arrayBuffer() {
		return new Uint8Array([97, 98, 99]).buffer
	}
}, 'abc.txt')

使用 AbortSignal 取消请求

你可以通过 AbortController 来取消请求。推荐的实现方式是使用 abort-controller

以下是一个在 150 毫秒后超时取消请求的示例实现:

import fetch, { AbortError } from 'node-fetch';

// AbortController was added in node v14.17.0 globally
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);
}

请参考测试用例获取更多示例。

API

fetch(url[, options])

  • url 表示请求URL的字符串
  • options HTTP(S)请求的配置项
  • 返回值: Promise<Response>

执行HTTP(S)请求。

url应为绝对URL,例如https://example.com/。若使用路径相对URL(/file/under/root)或协议相对URL(//can-be-http-or-https.com/),将导致返回被拒绝的Promise

配置项

各配置键的默认值显示在对应选项之后。

{
	// These properties are part of the Fetch Standard
	method: 'GET',
	headers: {},            // Request headers. format is the identical to that accepted by the Headers constructor (see below)
	body: null,             // Request body. can be null, or a Node.js Readable stream
	redirect: 'follow',     // Set to `manual` to extract redirect headers, `error` to reject redirect
	signal: null,           // Pass an instance of AbortSignal to optionally abort requests

	// The following properties are node-fetch extensions
	follow: 20,             // maximum redirect count. 0 to not follow redirect
	compress: true,         // support gzip/deflate content encoding. false to disable
	size: 0,                // maximum response body size in bytes. 0 to disable
	agent: null,            // http(s).Agent instance or function that returns an instance (see below)
	highWaterMark: 16384,   // the maximum number of bytes to store in the internal buffer before ceasing to read from the underlying resource.
	insecureHTTPParser: false	// Use an insecure HTTP parser that accepts invalid HTTP headers when `true`.
}

默认请求头

若未设置任何值,以下请求头将自动发送:

请求头
Accept-Encoding gzip, deflate, br(当 options.compress === true 时启用)
Accept */*
Content-Length (自动计算,若条件允许)
Host (根据目标 URI 自动填充主机和端口信息)
Transfer-Encoding chunked(当 req.body 为流式数据时启用)
User-Agent node-fetch

注意:当 bodyStream 类型时,Content-Length 不会自动设置。

自定义代理

通过 agent 选项可配置网络相关参数,这些配置超出 Fetch 标准范围,包括但不限于以下场景:

  • 支持自签名证书
  • 强制使用 IPv4 或 IPv6
  • 自定义 DNS 查询

详见 http.Agent 文档。

若未指定代理,将使用 Node.js 提供的默认代理。注意 Node.js 19 版本 开始默认启用 keepalive。如需在早期版本启用该功能,可参考以下代码示例覆盖代理配置。

此外,agent 选项支持接收函数,该函数根据当前 URL 返回 httphttpsAgent 实例,这在跨 HTTP/HTTPS 协议的重定向链中非常实用。

import http from 'node:http';
import https from 'node:https';

const httpAgent = new http.Agent({
	keepAlive: true
});
const httpsAgent = new https.Agent({
	keepAlive: true
});

const options = {
	agent: function(_parsedURL) {
		if (_parsedURL.protocol == 'http:') {
			return httpAgent;
		} else {
			return httpsAgent;
		}
	}
};

自定义高水位线

Node.js 中的流相较于客户端浏览器(大于 1MB,不同浏览器间不一致)具有较小的内部缓冲区大小(16KB,即 highWaterMark)。因此,当你在编写同构应用并使用 res.clone() 时,在 Node 环境下处理大响应时会出现挂起现象。

推荐的解决方案是并行处理克隆后的响应:

import fetch from 'node-fetch';

const response = await fetch('https://example.com');
const r1 = response.clone();

const results = await Promise.all([response.json(), r1.text()]);

console.log(results[0]);
console.log(results[1]);

如果你出于某些原因不喜欢上述解决方案,从 3.x 版本开始,你可以调整 highWaterMark 选项:

import fetch from 'node-fetch';

const response = await fetch('https://example.com', {
	// About 1MB
	highWaterMark: 1024 * 1024
});

const result = await res.clone().arrayBuffer();
console.dir(result);

不安全的 HTTP 解析器

该选项会透传给 http(s).request 的 insecureHTTPParser 参数。更多信息请参阅 http.request 文档。

手动重定向

当设置 redirect: 'manual' 选项时,node-fetch 的行为与浏览器及规范有所不同:

import fetch from 'node-fetch';

const response = await fetch('https://httpbin.org/status/301', { redirect: 'manual' });

if (response.status === 301 || response.status === 302) {
	const locationURL = new URL(response.headers.get('location'), response.url);
	const response2 = await fetch(locationURL, { redirect: 'manual' });
	console.dir(response2);
}

类:Request

一个包含 URL、方法、标头和正文信息的 HTTP(S) 请求。该类实现了 Body 接口。

由于 Node.js 的特性,以下属性目前未实现:

  • type
  • destination
  • mode
  • credentials
  • cache
  • integrity
  • keepalive

提供了以下 node-fetch 扩展属性:

  • follow
  • compress
  • counter
  • agent
  • highWaterMark

这些扩展的具体含义请参见 options

new Request(input[, options])

(符合规范)

  • input 表示 URL 的字符串,或另一个将被克隆的 Request 对象
  • options HTTP(S) 请求的 选项

构造一个新的 Request 对象。该构造函数与 浏览器 中的相同。

大多数情况下,直接使用 fetch(url, options) 比创建 Request 对象更简单。

类:Response

一个 HTTP(S) 响应。该类实现了 Body 接口。

以下属性目前在 node-fetch 中未实现:

  • trailer

new Response([body[, options]])

(符合规范)

构造一个新的 Response 对象。该构造函数与 浏览器 中的相同。

由于 Node.js 未实现服务工作者(该类专为此设计),很少需要直接构造 Response

response.ok

(符合规范)

表示请求是否正常结束的便捷属性。如果响应状态码大于等于 200 且小于 300,则为 true

response.redirected

(符合规范)

表示请求是否至少被重定向过一次的便捷属性。如果内部重定向计数器大于 0,则为 true

response.type

(与规范有差异)

表示响应类型的便捷属性。node-fetch 仅支持 'default''error',且未使用 过滤响应

类:Headers

该类允许操作和遍历一组 HTTP 标头。实现了 Fetch 标准 中指定的所有方法。

new Headers([init])

(符合规范)

  • init 可选参数,用于预填充 Headers 对象

构造一个新的 Headers 对象。init 可以是 nullHeaders 对象、键值映射对象或任何可迭代对象。

// Example adapted from https://fetch.spec.whatwg.org/#example-headers-class
import {Headers} from 'node-fetch';

const meta = {
	'Content-Type': 'text/xml'
};
const headers = new Headers(meta);

// The above is equivalent to
const meta = [['Content-Type', 'text/xml']];
const headers = new Headers(meta);

// You can in fact use any iterable objects, like a Map or even another Headers
const meta = new Map();
meta.set('Content-Type', 'text/xml');
const headers = new Headers(meta);
const copyOfHeaders = new Headers(headers);

接口: Body

Body 是一个抽象接口,其方法适用于 RequestResponse 类。

body.body

(与规范差异)

数据封装在 Body 对象中。请注意,虽然 Fetch 标准 要求该属性始终为 WHATWG ReadableStream,但在 node-fetch 中,它是一个 Node.js Readable

body.bodyUsed

(符合规范)

  • Boolean

一个布尔属性,表示此 body 是否已被消费。根据规范,已消费的 body 不能再次使用。

body.arrayBuffer()

body.formData()

body.blob()

body.json()

body.text()

fetch 提供了方法来解析 multipart/form-data 负载以及使用 .formData() 解析 x-www-form-urlencoded 的 body。这一设计源于 Service Worker 可以在消息发送到服务器之前拦截并修改它们。这对于构建服务器的开发者非常有用,可以用来解析和消费负载。

代码示例
import http from 'node:http'
import { Response } from 'node-fetch'

http.createServer(async function (req, res) {
  const formData = await new Response(req, {
    headers: req.headers // Pass along the boundary value
  }).formData()
  const allFields = [...formData]

  const file = formData.get('uploaded-files')
  const arrayBuffer = await file.arrayBuffer()
  const text = await file.text()
  const whatwgReadableStream = file.stream()

  // other was to consume the request could be to do:
  const json = await new Response(req).json()
  const text = await new Response(req).text()
  const arrayBuffer = await new Response(req).arrayBuffer()
  const blob = await new Response(req, {
    headers: req.headers // So that `type` inherits `Content-Type`
  }.blob()
})

类:FetchError

(node-fetch 扩展)

在数据获取过程中出现的操作错误。更多信息请参阅 ERROR-HANDLING.md

类:AbortError

(node-fetch 扩展)

当请求因响应 AbortSignalabort 事件而中止时抛出的错误。其 name 属性为 AbortError。更多详情请参见 ERROR-HANDLING.MD

TypeScript 支持

3.x 版本起,类型定义已内置在 node-fetch 中,无需额外安装类型包。

对于旧版本,请使用 DefinitelyTyped 提供的类型定义:

npm install --save-dev @types/node-fetch@2.x

致谢

感谢 github/fetch 提供了坚实的实现参考。

团队

David Frank Jimmy Wärting Antoni Kepinski Richie Bendall Gregor Martynus
David Frank Jimmy Wärting Antoni Kepinski Richie Bendall Gregor Martynus
前成员

许可证

MIT

项目介绍

可用于在 Node.js 环境中发起 HTTP 请求,实现与浏览器 fetch API 兼容的功能。支持原生 promise、异步函数及 Node 流,能自动解码内容编码,提供重定向限制、响应大小限制等实用扩展。【此简介由AI生成】

定制我的领域