Зачем нужен слой над fetch

В небольшом демо хватает fetch('/api/users'). В реальном приложении в каждом запросе повторяются одни и те же дела: базовый URL, заголовок Authorization, проверка response.ok, разбор JSON, таймаут, повторные попытки, единый формат ошибок. Если всё это скопировано в 40 файлов, смена формата токена превращается в правку 40 мест.

Решение — единый HTTP-клиент, через который идут все запросы. Путь два: готовая библиотека (Axios, ky, ofetch) или собственная обёртка на 30-50 строк поверх fetch.

Конвейер HTTP-клиента: request hook, сеть, response hook
Конвейер HTTP-клиента: request hook, сеть, response hook

💡 Код этого урока работает без сети: вместо fetch мы передаём фальшивую функцию (fetchImpl). Тот же приём понадобится и в тестах.


fetch и Axios

fetch есть в браузере и в Node 18+, дополнительный пакет не нужен. У него две известные особенности: на 404 и 500 промис не отклоняется, и нет таймаута. Axios закрывает эти пробелы, но добавляет код в бандл.

javascript
const res = await fetch('/api/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'Aysel' }),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const user = await res.json();

// Axios
import axios from 'axios';

const { data: user } = await axios.post('/api/users', {
  name: 'Aysel',
});
// non-2xx statuses reject the promise automatically
ТемаfetchAxios
4xx/5xxне отклоняет, проверяйте okотклоняет (меняется через validateStatus)
JSONвручнуюавтоматически, response.data
ТаймаутAbortControllerопция timeout
Interceptorsнетесть
Прогресс загрузкинетonUploadProgress

💡 Совет: в новом небольшом проекте — fetch и своя обёртка, в большом приложении со множеством эндпоинтов и interceptors — Axios или ky.


Axios: instance и interceptors

axios.create() возвращает независимый экземпляр с общей конфигурацией. Interceptors — функции, которые выполняются перед отправкой запроса и после получения ответа.

javascript
import axios from 'axios';

export const api = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 5000,
  headers: { Accept: 'application/json' },
});

api.interceptors.request.use((config) => {
  const token = localStorage.getItem('token');
  if (token) config.headers.Authorization = `Bearer ${token}`;
  return config;
});