首页 / Node.js 教程 / 内置 fetch 与 WebSocket 客户端

Node.js 教程

内置 fetch 与 WebSocket 客户端

本教程共 76 篇 · 第 29 篇 · 更新于 2026-07-25 · 约 5 分钟阅读

Node.jsfetchWebSocketundiciHTTP客户端

29. 内置 fetch 与 WebSocket 客户端

本节目标:用内置 fetch(undici)发请求,用 WebSocket 客户端做实时通信。

写服务端代码,免不了要调用别人的接口。以前我们装 node-fetchaxios,但从 v18 开始,Node.js 直接内置了浏览器标准的 fetch API。到 v24 LTS,它已经足够稳定,日常发请求不用再额外装包。

这一章讲两件事:用内置 fetch 做 HTTP 客户端,以及用内置 WebSocket 连外部的 WebSocket 服务。

内置 fetch 的来历

Node.js 的 fetch 并不是把浏览器的代码照搬过来,而是基于 Undici——一个专为 Node.js 从头写的 HTTP 客户端。它 bypass 了老旧的 http 内置模块,性能更好,语义也更现代。

Note

fetch 自 v18.0.0 起实验性引入,v21 默认开启,v24 LTS 已完全稳定。如果你的项目还在用 v16,那就得老老实实装 node-fetch@2

基础 GET 请求

语法和浏览器里一模一样:

const res = await fetch('https://api.github.com/users/nodejs');
const data = await res.json();
console.log(data.login, data.public_repos);

fetch 返回的是 Response 对象,常用方法有:

方法用途
res.json()按 JSON 解析
res.text()按纯文本返回
res.blob()返回 Blob 对象
res.arrayBuffer()返回 ArrayBuffer
res.ok状态码 200-299 时为 true
res.statusHTTP 状态码
res.headers响应头(Headers 对象)

注意一个跟 axios 不一样的地方:fetch 不会因为状态码是 404 或 500 就抛异常,只有网络故障才会 reject。所以拿到 Response 后,最好手动检查 res.ok

const res = await fetch('https://api.github.com/users/notexist-12345');
if (!res.ok) {
  console.error(`HTTP ${res.status}: ${res.statusText}`);
  return;
}
const data = await res.json();

POST 与自定义请求

第二个参数传一个配置对象,控制方法、头部和 body:

const res = await fetch('https://httpbin.org/post', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer token123',
  },
  body: JSON.stringify({ name: 'Tom', age: 20 }),
});

const data = await res.json();
console.log(data);

如果要发 application/x-www-form-urlencoded

const params = new URLSearchParams();
params.append('name', 'Tom');
params.append('age', '20');

const res = await fetch('https://httpbin.org/post', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: params,
});
Tip

URLSearchParams 直接当 body 传即可,fetch 会自动处理编码,不用手动 toString()

流式下载大文件

fetch 的 Response.body 是一个 ReadableStream,适合处理大文件,不用一次性塞进内存:

import { createWriteStream } from 'node:fs';

const res = await fetch('https://example.com/large-file.zip');
if (!res.ok) throw new Error('Download failed');

const file = createWriteStream('./large-file.zip');
for await (const chunk of res.body) {
  file.write(chunk);
}
file.end();
console.log('Download complete');

AbortController 取消请求

请求发出后想中途取消?用 AbortController

const controller = new AbortController();

// 3 秒后自动取消
setTimeout(() => controller.abort(), 3000);

try {
  const res = await fetch('https://httpbin.org/delay/10', {
    signal: controller.signal,
  });
  console.log(await res.json());
} catch (err) {
  if (err.name === 'AbortError') {
    console.log('Request was cancelled');
  } else {
    throw err;
  }
}

这个特性在做搜索联想、翻页请求时特别有用——旧请求还没回来,用户已经输入了新内容,这时候把旧请求取消掉,避免竞争。

超时处理

fetch 本身没有 timeout 选项,但配合 AbortController 很容易实现:

function fetchWithTimeout(url, options = {}, timeout = 5000) {
  const controller = new AbortController();
  const id = setTimeout(() => controller.abort(), timeout);

  return fetch(url, { ...options, signal: controller.signal })
    .finally(() => clearTimeout(id));
}

const res = await fetchWithTimeout('https://httpbin.org/delay/1', {}, 3000);
console.log(res.ok);

内置 WebSocket 客户端

Node.js 不仅能发 HTTP 请求,还能当 WebSocket 客户端。内置的 WebSocket 自 v21 引入,v22.4.0 起标记为稳定。注意,它只提供客户端能力,如果你想搭建 WebSocket 服务端(接受浏览器连接),还是需要 wsSocket.IO 这类库。

基本用法

const socket = new WebSocket('wss://echo.websocket.org/');

socket.addEventListener('open', () => {
  console.log('Connected');
  socket.send('Hello from Node.js');
});

socket.addEventListener('message', event => {
  console.log('Received:', event.data);
});

socket.addEventListener('close', event => {
  console.log('Connection closed', event.code, event.reason);
});

socket.addEventListener('error', err => {
  console.error('WebSocket error:', err.message);
});

收发 JSON

实际项目中,WebSocket 消息大多是 JSON:

const socket = new WebSocket('wss://example.com/socket');

socket.addEventListener('open', () => {
  socket.send(JSON.stringify({ type: 'subscribe', channel: 'price' }));
});

socket.addEventListener('message', event => {
  try {
    const msg = JSON.parse(event.data);
    console.log('Price update:', msg.price);
  } catch (err) {
    console.error('Invalid JSON:', event.data);
  }
});

手动关闭连接

// 正常关闭
socket.close(1000, 'Done');

// 常用关闭码
// 1000 - 正常关闭
// 1001 - 服务端或客户端离开
// 1006 - 异常断开(不会由代码主动发送)
// 1008 - 收到不符合格式的消息

fetch 与旧客户端的对比

如果你之前用 http.get 或第三方库,可能会关心这些差异:

特性内置 fetchhttp.get / request
API 风格Promise / async-await回调 / 流事件
返回值Response 对象IncomingMessage
自动 JSON手动 .json()手动拼接 buffer
取消请求AbortControllerreq.destroy()
浏览器兼容完全一致Node.js 独有

我个人的迁移经验是:新项目一律 fetch,老项目如果 axios 用得很深(拦截器、并发控制、自动重试),没必要为了换而换。但如果是写脚本、微服务、测试用例,内置 fetch 绝对是最省事的选择。