Fetch API в JavaScript: запросы, ошибки, таймауты и отмена
Практика3 минРедакция Innovialabs
Коротко
fetch отклоняет промис только при сетевой ошибке, поэтому статус ответа нужно проверять через response.ok вручную, а таймауты и отмену делать через AbortController или AbortSignal.timeout.
Главное о fetch
Fetch API — встроенный в браузеры и Node.js способ отправлять HTTP-запросы. Он возвращает промис с объектом Response, из которого вы читаете статус, заголовки и тело.
Три вещи, которые нужно знать сразу:
- fetch не выбрасывает ошибку на 404 или 500 — только на сетевой сбой или отмену;
- тело ответа читается отдельно:
response.json(),response.text(),response.blob(); - встроенного таймаута нет — его задают через AbortController.
GET-запрос
const res = await fetch('/api/products?category=phones');
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
const products = await res.json();
Параметры строки запроса удобно собирать через URLSearchParams, чтобы не экранировать значения вручную:
const params = new URLSearchParams({ q: 'чехол', page: '2' });
const res = await fetch(`/api/search?${params}`);
POST с JSON
const res = await fetch('/api/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ productId: 42, quantity: 1 }),
});
Без заголовка Content-Type: application/json многие серверы не поймут тело запроса.
POST с FormData
Для файлов и обычных HTML-форм используйте FormData:
const form = document.querySelector('form');
const res = await fetch('/api/upload', {
method: 'POST',
body: new FormData(form),
});
Не задавайте Content-Type вручную — браузер сам поставит multipart/form-data с правильной границей (boundary). Если указать заголовок самому, сервер не сможет разобрать тело.
Почему 404 и 500 — это не ошибка
С точки зрения fetch сервер ответил, значит, запрос выполнен. Статус приходится проверять самим. Удобно вынести это в одну функцию:
async function request(url, options = {}) {
const res = await fetch(url, options);
if (!res.ok) {
let details = null;
try {
details = await res.json();
} catch {
// тело не JSON — оставляем null
}
const error = new Error(`HTTP ${res.status}`);
error.status = res.status;
error.details = details;
throw error;
}
return res.status === 204 ? null : res.json();
}
Теперь в коде можно различать ситуации:
- TypeError — сеть недоступна, CORS-блокировка, неверный URL;
- AbortError или TimeoutError — запрос отменён или истёк таймаут;
- ошибка с полем
status— сервер ответил кодом 4xx или 5xx.
Таймауты и отмена
Самый короткий вариант — AbortSignal.timeout():
const res = await fetch('/api/report', {
signal: AbortSignal.timeout(5000),
});
Если нужно отменять запрос вручную (пользователь ушёл со страницы, ввёл новый поисковый запрос), используйте AbortController:
let controller;
async function search(query) {
controller?.abort();
controller = new AbortController();
try {
const res = await fetch(`/api/search?q=${encodeURIComponent(query)}`, {
signal: controller.signal,
});
return await res.json();
} catch (err) {
if (err.name === 'AbortError') return null;
throw err;
}
}
Так устаревшие ответы не перезапишут свежие результаты поиска.
Заголовки и cookies
- Токен авторизации передают в заголовке:
headers: { Authorization: 'Bearer ' + token }. - Параметр credentials управляет cookies: по умолчанию
same-origin, для запросов на другой домен нуженinclude. - При
credentials: 'include'сервер должен разрешить это в CORS-заголовках, иначе браузер заблокирует ответ.
Когда взять axios
| Задача | fetch | axios |
|---|---|---|
| Без зависимостей | Да | Нет, отдельная библиотека |
| Ошибка на 4xx/5xx | Нужна своя проверка | Из коробки |
| Интерсепторы (токены, логи) | Пишете обёртку сами | Встроены |
| Прогресс загрузки файлов | Неудобно | Есть |
| Автоматический JSON | Вызов res.json() | Автоматически |
Если в проекте нужна пара запросов — хватит fetch и небольшой обёртки. Если запросов много, есть единая логика обновления токенов и прогресс загрузки — axios сэкономит время.
Частые ошибки
- Не проверять
res.okи пытаться разобрать HTML страницы ошибки как JSON. - Ставить
Content-Typeдля FormData. - Забывать
awaitпередres.json(). - Не обрабатывать AbortError и показывать пользователю ошибку при обычной отмене.