Перейти к содержанию
Innovialabs

Fetch API в JavaScript: запросы, ошибки, таймауты и отмена

Практика3 мин

Коротко

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

Задачаfetchaxios
Без зависимостейДаНет, отдельная библиотека
Ошибка на 4xx/5xxНужна своя проверкаИз коробки
Интерсепторы (токены, логи)Пишете обёртку самиВстроены
Прогресс загрузки файловНеудобноЕсть
Автоматический JSONВызов res.json()Автоматически

Если в проекте нужна пара запросов — хватит fetch и небольшой обёртки. Если запросов много, есть единая логика обновления токенов и прогресс загрузки — axios сэкономит время.

Частые ошибки

  • Не проверять res.ok и пытаться разобрать HTML страницы ошибки как JSON.
  • Ставить Content-Type для FormData.
  • Забывать await перед res.json().
  • Не обрабатывать AbortError и показывать пользователю ошибку при обычной отмене.

Частые вопросы

Потому что сервер ответил, и с точки зрения сети запрос успешен. Проверяйте res.ok или res.status и выбрасывайте ошибку сами.

Поможем с этим

Если хотите не разбираться самим, а получить результат — этим мы занимаемся каждый день.

Обсудить проект

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.