三个选项
| 库 | Node 版本 | 特点 |
|---|---|---|
| 原生 fetch | 18+ | 全局可用 / Web 标准 |
| undici | 任意 | fetch 底层实现,更细控制 |
| axios | 任意 | 老牌、生态成熟 / 拦截器 / 自动 JSON |
新项目优先 fetch(无需装包);需要拦截器 / 重试用 axios;极致性能 / Connection Pool 用 undici。
fetch 基础
const r = await fetch('https://api.example.com/users');
const users = await r.json();
POST JSON
const r = await fetch('https://api.example.com/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Alice' }),
});
const created = await r.json();
POST FormData
const form = new FormData();
form.append('name', 'Alice');
form.append('file', new Blob([buf]), 'photo.jpg');
await fetch('/upload', { method: 'POST', body: form });
自定义 headers
await fetch('/api', {
headers: {
Authorization: `Bearer ${token}`,
'X-API-Version': '2',
},
});
检查状态
const r = await fetch(url);
if (!r.ok) { // ok = status 200-299
throw new Error(`HTTP ${r.status}: ${r.statusText}`);
}
⚠ fetch 不会因 4xx/5xx 自动抛错 —— 必须自己检查 r.ok。
超时(AbortController)
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 5000);
try {
const r = await fetch(url, { signal: controller.signal });
// ...
} catch (err) {
if (err.name === 'AbortError') {
console.error('Timeout');
}
} finally {
clearTimeout(timer);
}
流式响应
const r = await fetch('https://example.com/big-file');
const stream = r.body; // Web ReadableStream
// 转 Node Stream + pipe 到文件
import { Readable } from 'stream';
import { createWriteStream } from 'fs';
await pipeline(
Readable.fromWeb(stream),
createWriteStream('big.dat'),
);
undici:fetch 底层 + 高级控制
npm install undici
import { request, Pool } from 'undici';
// 简单请求
const { statusCode, body } = await request('https://api.example.com');
const data = await body.json();
// 连接池(高 QPS 场景)
const pool = new Pool('https://api.example.com', {
connections: 10,
});
const { body } = await pool.request({
path: '/users',
method: 'GET',
});
适合:高 QPS、需要复用连接、需要更多控制。
axios:传统选择
npm install axios
import axios from 'axios';
const { data } = await axios.get('/users'); // 自动 JSON
const { data: created } = await axios.post('/users', { name: 'Alice' });
// 拦截器(统一加 token / 处理错误)
axios.interceptors.request.use(config => {
config.headers.Authorization = `Bearer ${getToken()}`;
return config;
});
axios.interceptors.response.use(
res => res,
err => {
if (err.response?.status === 401) {
// 跳登录
}
return Promise.reject(err);
}
);
axios 优势:
- 默认带 JSON 解析
- 拦截器
- 4xx/5xx 自动抛错(不用判断 .ok)
- 自带超时配置(不用 AbortController)
重试机制
原生 fetch 不带重试——自己写或用库:
async function fetchRetry(url, options = {}, retries = 3, delay = 1000) {
for (let i = 0; i <= retries; i++) {
try {
const r = await fetch(url, options);
if (r.ok) return r;
if (i === retries) throw new Error(`HTTP ${r.status}`);
} catch (err) {
if (i === retries) throw err;
}
await new Promise(r => setTimeout(r, delay * (i + 1))); // 退避
}
}
或用 p-retry 库。
并发限流
import pLimit from 'p-limit';
const limit = pLimit(10); // 同时最多 10 个
const results = await Promise.all(
urls.map(url => limit(() => fetch(url)))
);
坑
- fetch 不抛 4xx/5xx 错误——必须查
r.ok - response 流只能消费一次——
r.json()之后再r.text()失败 - 没设超时 → 慢请求拖死服务——永远加 AbortController 或客户端超时
- 大响应
.json()把整个加载到内存——大文件用 stream
下一篇:child_process。