ФронтендБэкендJavaScript

Fetch API в JavaScript: GET, POST и обработка ошибок на рабочем примере

Практическое руководство по Fetch API: GET, POST JSON, response.ok, try catch, таймаут через AbortController и состояния интерфейса.

Кодик

Автор

5 мин чтения

Рабочий запрос через Fetch API состоит из четырёх обязательных действий: дождаться fetch, проверить response.ok, прочитать тело подходящим методом и обработать ошибку интерфейсом. fetch отклоняет Promise при части сетевых сбоев, но обычный ответ 404 или 500 сам по себе не попадает в catch. Поэтому проверка статуса должна быть написана явно до response.json().

Fetch API предоставляет Promise-интерфейс для HTTP-запросов в браузере. GET обычно читает ресурс, POST отправляет данные в теле. Но хороший пример должен показывать не только счастливый путь: пользователь должен увидеть загрузку, серверную ошибку, сетевой сбой и корректный результат, а кнопка не должна отправить десять одинаковых запросов.

1Запрос

URL, метод, заголовки и тело выражают контракт API.

2Ответ

Статус проверяется до чтения JSON или текста.

3Интерфейс

loading, success, empty и error являются отдельными состояниями.

Минимальный GET-запрос с правильной проверкой

Без второго аргумента fetch(url) выполняет GET. Возвращаемый Promise разрешается объектом Response, когда браузер получил статус и заголовки. Метод response.json() тоже асинхронный и читает тело один раз. Между этими шагами проверьте ok, который равен true для статусов диапазона 200.

async function loadTasks() {
  const response = await fetch('/api/tasks');

  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }

  const tasks = await response.json();
  return tasks;
}
ШагЧто возвращаетсяЧастая ошибка
fetch(url)Promise<Response>Забыть await
response.okБулево значениеСчитать 404 автоматическим reject
response.statusHTTP-кодНе сохранить код для сообщения
response.json()Promise с разобранным теломЗабыть второй await
catchСетевые и брошенные ошибкиПоказать только console.log

Этапы Fetch API от запроса до данных интерфейса
Статус проверяется до чтения тела, а результат проходит в отдельное состояние интерфейса.

Проверяйте Content-Type, если API иногда возвращает HTML. Прокси или сервер при ошибке может прислать HTML-страницу. Тогда response.json() даст SyntaxError и скроет исходный статус. Для общей обёртки проверьте заголовок content-type и читайте текст, если JSON не обещан.

POST с JSON: method, headers и body

Соберите запрос по контракту
  1. Проверьте URL и HTTP-метод в документации API.
  2. Создайте обычный объект данных без DOM-узлов и функций.
  3. Укажите Content-Type: application/json, если сервер ждёт JSON.
  4. Преобразуйте объект через JSON.stringify в body.
  5. Проверьте response.ok до чтения ответа.
  6. Отобразите результат и верните кнопке доступное состояние.

GET не должен иметь тело. Для POST объект JavaScript нужно сериализовать, иначе в сеть может уйти строка [object Object]. Заголовок Content-Type сообщает серверу формат тела, но сам не преобразует данные. Метод и формат должны соответствовать документации конкретного endpoint.

async function createTask(title) {
  const response = await fetch('/api/tasks', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ title }),
  });

  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json();
}

Сравнение GET и POST запросов Fetch API
GET получает ресурс, а POST добавляет метод, формат и сериализованное тело.

Network показывает фактический запрос, а не намерение кода

После отправки откройте DevTools и проверьте Request Method, Request Payload, Content-Type, статус и Response. Если тело выглядит иначе, чем ожидалось, исправляйте сериализацию до поиска проблемы на сервере.

Как разделить HTTP, сеть, JSON и отмену

У запроса несколько независимых точек отказа. Сеть может быть недоступна, сервер может вернуть 500, тело может оказаться невалидным JSON, а пользователь может уйти со страницы и отменить операцию. Одно сообщение «что-то пошло не так» удобно для первого прототипа, но бесполезно для диагностики.

СлойПримерКак обнаружить
СетьНет соединения или DNSfetch отклоняет Promise
HTTP404, 401, 500!response.ok
ФорматВместо JSON пришёл HTMLОшибка response.json() и Content-Type
CORSОтвет закрыт политикойConsole и Network
ОтменаAbortControllerОшибка с именем AbortError
async function requestJSON(url, options = {}) {
  const response = await fetch(url, options);
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }

  const type = response.headers.get('content-type') || '';
  if (!type.includes('application/json')) {
    throw new TypeError('Сервер вернул не JSON');
  }
  return response.json();
}

Сетевые HTTP JSON CORS и Abort ошибки Fetch API
Разные слои отказа обнаруживаются разными проверками.

Не показывайте пользователю весь объект Error. В интерфейсе достаточно понятного действия: повторить запрос, войти или исправить поле. Для разработчика сохраните класс, сообщение, статус и URL без токенов и персональных данных. Эти два уровня сообщений решают разные задачи.

Ошибки Fetch API, которые проходят незаметно

Код без await выводит Promise вместо данных. Код без проверки ok пытается обработать тело ошибки как успешный объект. Бесконечный индикатор появляется, когда состояние loading выключается только в счастливой ветке. Для интерфейса удобно использовать try, catch и finally.

Один await вместо двух

И fetch, и response.json возвращают Promise. Дождаться нужно обоих этапов.

Считать 404 исключением fetch

HTTP-ответ получен успешно на сетевом уровне. Проверьте response.ok самостоятельно.

Передавать объект прямо в body

Сериализуйте JSON и укажите соответствующий Content-Type.

Оставлять кнопку активной

Повторные клики создают дубликаты POST. Отключайте действие на время запроса.

finally возвращает интерфейс из состояния загрузки

Включите loading перед запросом и выключайте в finally. Этот блок выполняется после успеха и после ошибки. Отдельно решите, можно ли повторить запрос автоматически: для POST повтор может создать дубликат без поддержки идемпотентности сервером.

Практика: загрузка и создание задач на странице

Соберите модуль с двумя функциями: loadTasks для GET и createTask для POST. Обработчик формы отвечает только за интерфейс: читает поле, блокирует кнопку, вызывает API-функцию, обновляет список и показывает ошибку. Такое разделение упрощает тестирование.

Чек-лист рабочего запроса
  1. URL и метод совпадают с контрактом endpoint.
  2. POST-тело сериализовано, а Content-Type соответствует формату.
  3. После fetch явно проверяется response.ok.
  4. Тело читается ровно один раз подходящим методом.
  5. Интерфейс показывает loading, empty, success и error.
  6. Кнопка возвращается в активное состояние через finally.
Тестируйте как минимум четыре ответа. Проверьте 200 с данными, 200 с пустым массивом, 4xx с понятной ошибкой и сетевой сбой. Один успешный запрос подтверждает только счастливую ветку и ничего не говорит об устойчивости интерфейса.
Короткие ответы
Какой метод использует fetch по умолчанию?

GET.

Попадает ли ответ 404 в catch автоматически?

Нет. fetch обычно возвращает Response, поэтому нужно проверить response.ok.

Почему у response.json нужен await?

Чтение и разбор тела асинхронны, метод возвращает Promise.

Что передавать в body для JSON?

Строку из JSON.stringify и заголовок Content-Type: application/json.

Как отменить долгий запрос?

Передать signal от AbortController и вызвать abort, когда запрос больше не нужен.

Свяжите запрос с состояниями интерфейса

Закрепите асинхронный JavaScript в курсе JavaScript и сравните пример с пошаговым руководством по API. Вывод результатов удобно строить по статье о DOM-манипуляциях.

Если браузер блокирует другой origin, откройте материал от fetch до обработки API в React. В Кодике храните минимальный endpoint, ожидаемый статус и пример JSON как проверяемый контракт.