Зачем нужен слой над fetch
В небольшом демо хватает fetch('/api/users'). В реальном приложении в каждом запросе повторяются одни и те же дела: базовый URL, заголовок Authorization, проверка response.ok, разбор JSON, таймаут, повторные попытки, единый формат ошибок. Если всё это скопировано в 40 файлов, смена формата токена превращается в правку 40 мест.
Решение — единый HTTP-клиент, через который идут все запросы. Путь два: готовая библиотека (Axios, ky, ofetch) или собственная обёртка на 30-50 строк поверх fetch.
💡 Код этого урока работает без сети: вместо
fetchмы передаём фальшивую функцию (fetchImpl). Тот же приём понадобится и в тестах.
fetch и Axios
fetch есть в браузере и в Node 18+, дополнительный пакет не нужен. У него две известные особенности: на 404 и 500 промис не отклоняется, и нет таймаута. Axios закрывает эти пробелы, но добавляет код в бандл.
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| Тема | fetch | Axios |
|---|---|---|
| 4xx/5xx | не отклоняет, проверяйте ok | отклоняет (меняется через validateStatus) |
| JSON | вручную | автоматически, response.data |
| Таймаут | AbortController | опция timeout |
| Interceptors | нет | есть |
| Прогресс загрузки | нет | onUploadProgress |
💡 Совет: в новом небольшом проекте —
fetchи своя обёртка, в большом приложении со множеством эндпоинтов и interceptors — Axios или ky.
Axios: instance и interceptors
axios.create() возвращает независимый экземпляр с общей конфигурацией. Interceptors — функции, которые выполняются перед отправкой запроса и после получения ответа.
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;
});