三个选项

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。