Fetch API в JavaScript: понятное руководство с примерами, ошибками и лучшими практиками

Fetch API в JavaScript — стандартный способ делать HTTP-запросы без дополнительных библиотек. Он прост в основе, но имеет нюансы: статусы ошибок, тайм-ауты, отмена, CORS, повторные попытки, загрузка и скачивание файлов. В этом руководстве вы получите рабочие примеры и чек-лист лучших практик.
Быстрый старт: GET JSON и проверка статуса
Важный момент: fetch не бросает исключение на 4xx/5xx по умолчанию — нужно проверять res.ok
.
fetch('https://api.example.com/users')
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
})
.then(data => console.log(data))
.catch(err => console.error('Ошибка запроса:', err));
Универсальный helper fetchJson
Создадим мини-обёртку, которая гарантирует исключение на ошибочном статусе и парсит JSON.
async function fetchJson(url, options = {}) {
const res = await fetch(url, options);
if (!res.ok) {
const text = await res.text().catch(() => '');
throw new Error(`HTTP ${res.status} ${res.statusText} ${text ? '- ' + text.slice(0,200) : ''}`);
}
return res.json();
}
POST запрос: отправка JSON
Не забывайте заголовок Content-Type
и сериализацию тела.
async function login(email, password) {
return fetchJson('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
}
Тайм-ауты и отмена запросов: AbortController
По умолчанию у fetch нет тайм-аута. Добавим свой через AbortController.
async function fetchWithTimeout(url, opts = {}, ms = 8000) {
const ctrl = new AbortController();
const id = setTimeout(() => ctrl.abort(), ms);
try {
const res = await fetch(url, { ...opts, signal: ctrl.signal });
return res;
} finally {
clearTimeout(id);
}
}
// Пример использования
try {
const res = await fetchWithTimeout('/api/data', {}, 5000);
const data = await res.json();
console.log(data);
} catch (e) {
if (e.name === 'AbortError') console.warn('Запрос отменён по тайм-ауту');
else console.error(e);
}
Повторы с экспоненциальным бэкоффом
Полезно при временных сетевых сбоях и 5xx-ответах сервера.
async function fetchRetry(url, options = {}, retries = 3, backoff = 500) {
let lastErr;
for (let i = 0; i <= retries; i++) {
try {
return await fetchJson(url, options);
} catch (e) {
lastErr = e;
if (i === retries) break;
const delay = backoff * 2 ** i + Math.random() * 100;
await new Promise(r => setTimeout(r, delay));
}
}
throw lastErr;
}
Загрузка файлов: FormData
Для загрузки файлов используйте FormData
— заголовок Content-Type выставится автоматически.
async function uploadAvatar(file) {
const fd = new FormData();
fd.append('avatar', file);
fd.append('name', 'Alex');
const res = await fetch('/upload', { method: 'POST', body: fd });
if (!res.ok) throw new Error('Ошибка загрузки');
return res.json();
}
Скачивание и прогресс загрузки
Прогресс скачивания можно отслеживать через ReadableStream
(поддерживается в современных браузерах).
async function downloadWithProgress(url) {
const res = await fetch(url);
if (!res.ok) throw new Error('Network error');
const reader = res.body.getReader();
const total = +res.headers.get('Content-Length') || 0;
let received = 0;
const chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
received += value.length;
if (total) console.log(`Прогресс: ${Math.round(received / total * 100)}%`);
}
const blob = new Blob(chunks);
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'file.zip';
a.click();
}
Параллельные запросы и простой кэш
// Параллельно получим пользователя и его посты
const [user, posts] = await Promise.all([
fetchJson('/api/user/42'),
fetchJson('/api/user/42/posts')
]);
// Простейший кэш в localStorage с TTL
async function fetchCached(key, url, ttlMs = 60000) {
const raw = localStorage.getItem(key);
if (raw) {
const { data, ts } = JSON.parse(raw);
if (Date.now() - ts < ttlMs) return data;
}
const data = await fetchJson(url);
localStorage.setItem(key, JSON.stringify({ data, ts: Date.now() }));
return data;
}
CORS и аутентификация
Если обращаетесь к стороннему домену, сервер должен вернуть корректные заголовки: Access-Control-Allow-Origin
, а при куках — также Access-Control-Allow-Credentials: true
и точное значение Origin (не *). На клиенте включайте режимы при необходимости:
await fetch('https://api.example.com/profile', {
mode: 'cors',
credentials: 'include' // отправка и получение кук
});
Частые ошибки и как их избежать
- Забыли проверять
res.ok
— используйте обёртку. - Нет тайм-аута — добавьте AbortController.
- Случайно перезаписали
headers
— объединяйте их аккуратно, учитываяContent-Type
. - Отправляете JSON без
JSON.stringify
— тело должно быть строкой. - Путаете CORS и аутентификацию — настройте и серверные заголовки, и
credentials
на клиенте. - Игнорируете backoff — добавьте повторы для временных сбоев.
Хотите быстрее закрепить эти навыки в реальных задачах? Прокачать JavaScript на практике — перейти к интенсивному курсу. Там вы шаг за шагом отрабатываете основы и уверенно переходите к продвинутым приёмам.
Резюме
Fetch API в JavaScript — мощный базовый инструмент. Используйте проверку статуса, обёртки для JSON, тайм-ауты и отмену, повторы с бэкоффом, FormData для загрузки, потоки для прогресса скачивания, параллельные запросы и кэш. Следуя этим практикам, вы получите надёжный сетевой слой, который проще поддерживать и масштабировать.
-
-
Михаил Русаков
Комментарии (0):
Для добавления комментариев надо войти в систему.
Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.