lenec ru

← все посты

SolidJS: реактивность без Virtual DOM — практическое сравнение с React

10K

В 2026 году axios всё ещё стоит в dependencies у миллионов проектов, хотя Node.js давно имеет встроенный HTTP-клиент. Начиная с Node 21 fetch() доступен без флагов, а под капотом работает undici — быстрый HTTP/1.1-клиент, написанный с нуля для Node.js. Разбираемся, зачем тянуть лишнюю зависимость, если всё есть из коробки.

Native fetch в Node 21+ — что под капотом

Глобальный fetch() в Node.js — обёртка над библиотекой undici, которая заменила устаревший http.request в качестве рекомендуемого HTTP-клиента.

  • Работает поверх собственного HTTP-парсера llhttp.
  • Поддерживает HTTP/1.1 pipelining и connection pooling из коробки.
  • Не имеет ограничений CORS (серверная среда).
  • Поддерживает AbortController для отмены запросов.
// Node 21+ — никаких импортов
const response = await fetch('https://api.example.com/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'test' }),
  signal: AbortSignal.timeout(5000)
});
const data = await response.json();

undici.request vs fetch vs axios — бенчмарки

Undici предоставляет два уровня API: высокоуровневый fetch() и низкоуровневый undici.request(). Бенчмарк на 10 000 последовательных GET к локальному серверу (Node 22, Linux):

Клиент            | req/s   | p99 latency | RSS
──────────────────────────────────────────────────
undici.request()  | 48 200  | 2.1 ms      | 52 MB
global fetch()    | 41 500  | 2.8 ms      | 61 MB
axios 1.7         | 22 300  | 5.4 ms      | 89 MB
node-fetch 3.x    | 28 100  | 4.1 ms      | 74 MB

Undici быстрее axios в 2x за счёт переиспользования соединений и оптимизированного парсера. Разница в API:

import { request } from 'undici';

// undici.request — возвращает statusCode + body stream
const { statusCode, body } = await request('https://api.example.com/data');
const json = await body.json();

// fetch — стандартный Web API
const res = await fetch('https://api.example.com/data');
const json2 = await res.json();

Connection pooling и keep-alive

Главное преимущество undici — управление пулом соединений. Для высоконагруженных сценариев настройте Agent явно:

import { Pool, Agent, setGlobalDispatcher } from 'undici';

const agent = new Agent({
  keepAliveTimeout: 30_000,
  keepAliveMaxTimeout: 60_000,
  connections: 128,
  pipelining: 1
});
setGlobalDispatcher(agent);

// Pool для конкретного хоста
const pool = new Pool('https://api.example.com', {
  connections: 64,
  keepAliveTimeout: 20_000
});

const { body } = await pool.request({ path: '/users', method: 'GET' });
const users = await body.json();

Правильная настройка пула даёт прирост в 3-5x по сравнению с дефолтным axios, который создаёт новое соединение на каждый запрос без явной настройки.

Retry, timeout, abort — паттерны надёжного клиента

Native fetch не имеет встроенного retry. В undici 6+ есть RetryAgent:

import { RetryAgent, Agent } from 'undici';

const retryAgent = new RetryAgent(new Agent(), {
  maxRetries: 3,
  minTimeout: 500,
  maxTimeout: 5000,
  timeoutFactor: 2,
  statusCodes: [429, 500, 502, 503, 504]
});
setGlobalDispatcher(retryAgent);

Таймауты и отмена:

// Таймаут через AbortSignal
const res = await fetch(url, {
  signal: AbortSignal.timeout(10_000)
});

// Комбинация: таймаут + ручная отмена (Node 20+)
const controller = new AbortController();
const res = await fetch(url, {
  signal: AbortSignal.any([
    AbortSignal.timeout(10_000),
    controller.signal
  ])
});

Streaming response и большие body

Undici нативно работает со стримами — можно скачивать гигабайтные файлы без буферизации в память:

import { pipeline } from 'node:stream/promises';
import { createWriteStream } from 'node:fs';
import { request } from 'undici';

const { body } = await request('https://releases.example.com/archive.tar.gz');
await pipeline(body, createWriteStream('./archive.tar.gz'));

Для NDJSON/SSE-стримов через fetch:

const res = await fetch('https://api.example.com/stream');
const reader = res.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  const lines = decoder.decode(value).split('\n');
  for (const line of lines) {
    if (line.trim()) console.log(JSON.parse(line));
  }
}

Когда всё-таки нужен axios

  • Интерцепторы — глобальная обработка запросов/ответов. В undici аналог — кастомный Dispatcher.
  • Прогресс загрузки — axios имеет onUploadProgress, в fetch нужно считать через stream.
  • Легаси — если кодовая база завязана на axios API, миграция может не окупиться.

Для новых проектов в 2026 году native fetch + undici — рациональный выбор: ноль зависимостей, лучшая производительность, стандартный API.

Комментарии 0

  • Будьте первым, кто оставит комментарий.

Войдите, чтобы оставить комментарий.