Fetch — промис-ориентированный HTTP-клиент
fetch встроен в браузеры и современные версии Node. Передаёте ему URL — получаете Promise, который резолвится объектом Response. По сути, это и есть весь API:
Два вызова .then здесь не случайно — у нас два асинхронных шага. Сначала приходят заголовки ответа (именно этим резолвится первый промис), а уже потом читается и парсится тело ответа (response.json() сам по себе возвращает промис). Тело не загружается до тех пор, пока вы явно его не запросите.
Тот же самый код через async/await читается как обычный, сверху вниз:
Два await — две точки приостановки. Работа та же, но порядок чтения гораздо понятнее.
Объект Response
Важный момент: fetch возвращает вам не тело ответа, а объект Response — с метаданными и набором методов, которые позволяют прочитать тело в нужном формате:
Тело ответа можно прочитать через .json(), .text(), .blob(), .arrayBuffer() или .formData() — каждый из этих методов возвращает промис. Важный нюанс: тело читается ровно один раз. Если дважды вызвать .json() на одном и том же Response, второй вызов выбросит ошибку.
Главная ловушка: HTTP-ошибки не вызывают reject
На этом спотыкается почти каждый, кто только начинает работать с fetch. Ответ 404 или 500 — это не реджект промиса. Промис спокойно резолвится, просто response.ok будет равен false. Fetch отклоняется только тогда, когда сам запрос не удалось выполнить: упал DNS, нет сети, заблокировал CORS.
В итоге наивный код радостно проглотит страницу с ошибкой и уже потом упадёт на .json():
Чтобы это исправить, проверяйте response.ok вручную и выбрасывайте исключение, если сервер вернул код ошибки:
Привыкайте писать этот блок if (!response.ok) — он должен быть в каждой обёртке над fetch, которую вы пишете.
POST-запрос через fetch
По умолчанию fetch отправляет GET. Для любого другого метода передайте второй аргумент — объект с настройками:
Отметим три важных момента:
methodпо умолчанию —"GET". Для POST, PUT, DELETE и PATCH его нужно указать явно.- В
bodyпередаётся строка (илиFormData,Blobи т.п.) — fetch сам объекты не сериализует.JSON.stringify(...)— ваша забота. - Заголовок
Content-Typeговорит серверу, как разбирать тело запроса. Забудете — и большинство серверов воспримут тело как обычный текст.
Заголовки, query-параметры и другие опции
Заголовки — это обычный объект (или экземпляр Headers). Query-строку собираем руками, чаще всего через URLSearchParams:
URLSearchParams сам позаботится о кодировании — пробелы, амперсанды, юникод, — так что URL не развалится, даже если во входных данных есть символы, требующие экранирования.
Ещё пара опций, которые часто встречаются в реальном коде: credentials: "include" — чтобы отправлять куки при кросс-доменных запросах, cache: "no-store" — чтобы обойти HTTP-кэш, и mode: "cors" (обычно стоит по умолчанию) — для управления поведением CORS.
Отмена запроса через AbortController
Бывает, что запрос нужно просто прервать — пользователь ввёл новый поисковый запрос или ответ слишком долго не приходит. Для этого в fetch есть AbortController:
controller.abort() приводит к тому, что промис fetch отклоняется с DOMException, у которого name равен "AbortError". Блок finally очищает таймер — чтобы после успешного запроса не остался висеть забытый setTimeout.
Такую связку — fetch, таймаут и очистка — имеет смысл завернуть в отдельную функцию и переиспользовать по всему проекту.
Переиспользуемая обёртка над fetch
Соберём всё вместе — получится небольшой хелпер, который берёт на себя всю рутину:
Одно место для заголовков, одно — для обработки ошибок, одно — для работы с пустыми ответами. В любом мало-мальски серьёзном приложении в итоге появляется что-то подобное.
Дальше: обработка ошибок в асинхронном коде
Fetch — одно из самых частых мест, где всплывают асинхронные ошибки, и проверка response.ok здесь лишь кусочек пазла. Следующая страница — про обработку ошибок в промисах и async/await: куда уходят ошибки, как их ловить и какие ловушки позволяют им молча проскользнуть мимо.
Часто задаваемые вопросы
Как пользоваться fetch в JavaScript?
Вызываете fetch(url) с нужным адресом — получаете Promise, который разрешится объектом Response. Чтобы достать тело ответа, вызывайте response.json() (это тоже промис). Через async/await выглядит так: const res = await fetch(url); const data = await res.json();.
Как отправить POST-запрос через fetch?
Вторым аргументом передайте объект с опциями: method: 'POST', заголовки (обычно 'Content-Type': 'application/json') и body. Объекты нужно вручную превратить в строку через JSON.stringify(...) — fetch сам тело не сериализует.
Почему fetch не падает на 404 или 500?
Fetch реджектит промис только при сетевых проблемах — DNS не разрешился, нет соединения, CORS заблокировал. Для fetch ответ со статусом 404 или 500 — это всё ещё успешно полученный ответ. Проверять нужно самому: response.ok (true при статусах 200–299) или response.status, и при ошибке кидать исключение вручную.
Можно ли отменить запрос fetch?
Да, через AbortController. Создаёте контроллер, в опциях fetch передаёте его signal, а когда нужно отменить запрос — вызываете controller.abort(). Промис fetch упадёт с AbortError, который ловится в обычном catch.