Fetch в JavaScript: запросы к серверу без боли
Содержание
О проверке кода. Примеры не запускались в браузере — в отличие от материалов по Node.js с фактическим выводом. Говорим прямо, а не ставим бейдж «проверено».
Общение с сервером — сердце современного веба. fetch заменил громоздкий XMLHttpRequest и внешние библиотеки простым API на промисах. Разберём его правильное применение.
Простой GET-запрос
async function loadUser(id) {
const response = await fetch(`/api/users/${id}`); // 1. ждём ответ
const user = await response.json(); // 2. ждём разбор тела
return user;
}
Два await — не случайность. Первый ждёт, пока сервер вернёт ответ (заголовки, статус). Второй — response.json() — ждёт загрузки и разбора тела: оно приходит потоком отдельно от заголовков, поэтому его получение тоже асинхронно. Кроме .json() есть .text(), .blob() (файлы), .formData().
Главная ловушка: fetch не бросает на 404
// ❌ наивно — обработает ошибку сервера как успех
const response = await fetch('/api/users/999');
const user = await response.json(); // 404 придёт сюда как «успех»!
// ✅ проверяем статус вручную
const response = await fetch('/api/users/999');
if (!response.ok) {
throw new Error(`HTTP ${response.status}`); // 404, 500 — сами бросаем
}
const user = await response.json();
fetch отклоняет промис только при сетевом сбое — нет интернета, оборвано соединение, DNS не разрешился. HTTP-ошибки 404 и 500 для него — успешный ответ сервера, ведь ответ пришёл. Поэтому статус проверяют вручную через response.ok (true для 200–299). Это отличие от библиотек вроде axios, которые бросают на ошибочных статусах, и частый источник багов.
- 1 await fetch(url)Ждём ответ сервера
- 2 Проверить response.ok404/500 fetch НЕ считает ошибкой — проверяем сами
- 3 await response.json()Разбираем тело ответа
- 4 try/catch вокруг всегоЛовим сетевые сбои и брошенные ошибки
POST с JSON
async function createUser(data) {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data), // объект → строка JSON
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
}
createUser({ name: 'Аня', age: 30 });
Отправка данных требует трёх вещей: method: 'POST', заголовок Content-Type: application/json и тело body с JSON.stringify(data) — объект нужно вручную превратить в строку. Забыть заголовок или stringify — частая ошибка, сервер не поймёт данные. Аналогично шлют PUT, PATCH, DELETE.
Полная обработка ошибок
async function loadData(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Сервер вернул ${response.status}`);
}
return await response.json();
} catch (error) {
// сюда попадут И сетевые сбои, И брошенный выше HTTP-статус
console.error('Ошибка загрузки:', error.message);
return null;
}
}
try/catch вокруг всего ловит два вида проблем: сетевые сбои (их бросает сам fetch) и HTTP-ошибки (которые бросаете вы после проверки response.ok). Это обычная обработка ошибок async/await — асинхронный код ловят синхронно выглядящим try/catch.
Отмена запроса
const controller = new AbortController();
fetch('/api/search?q=node', { signal: controller.signal })
.then(r => r.json())
.catch(e => {
if (e.name === 'AbortError') console.log('запрос отменён');
});
// отменить — например, пользователь ввёл новый запрос
controller.abort();
AbortController отменяет запрос на лету. Передаёте signal в fetch, а вызов controller.abort() прерывает запрос. Классический случай — живой поиск: пользователь печатает, каждый новый символ шлёт запрос, а предыдущий (уже неактуальный) отменяют. Это экономит трафик и предотвращает гонку ответов. Часто сочетается с троттлингом ввода.
Параллельные запросы
// ❌ последовательно — медленно (сумма времён)
const user = await (await fetch('/api/user')).json();
const posts = await (await fetch('/api/posts')).json();
// ✅ параллельно — быстро (время самого долгого)
const [user, posts] = await Promise.all([
fetch('/api/user').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
]);
Если запросы не зависят друг от друга, не ждите их по очереди. Promise.all запускает все сразу, и общее время равно самому долгому запросу, а не их сумме. Деструктуризация массива раскладывает результаты по переменным. Это одна из главных оптимизаций в работе с сетью.
Смежные темы: промисы и async/await — основа fetch; JSON — формат данных; обработка ошибок; debounce и throttle — для живого поиска. Полный список — в уроках JavaScript.