Fetch в JavaScript: как отправить GET и POST-запрос и обработать ответ

Fetch отправляет HTTP-запрос и возвращает промис с объектом Response, а данные из него достают через response.json().

Разбираем GET и POST с JSON, параметры и формы, обработку ошибок, таймаут через AbortSignal и CORS. Все примеры можно запустить в консоли браузера.

Статью написал:
Ваня Буявец, продюсер, основатель Checkroi
Ваня Буявец
Основатель Checkroi, продюсер, эксперт в выборе онлайн-курсов
Все 3100 статей автора Подписаться на Телеграм-канал
Одобрено экспертом:
Наташа Буявец, основатель Checkroi, эксперт по онлайн-курсам
Наташа Буявец
Основательница Checkroi, продюсер Youtube-каналов, эксперт по онлайн-курсам
Все 3754 экспертных мнения Подписаться на Телеграм-канал
Обложка: Fetch в JavaScript: как отправить GET и POST-запрос и обработать ответ

Чтобы отправить HTTP-запрос из JavaScript, вызовите fetch(url, options). Функция сразу возвращает промис, а когда сервер ответит, промис выполнится объектом Response. Данные из него достают отдельным вызовом: await response.json() для JSON, response.text() для текста.

Ниже разберём GET и POST-запросы, заголовки, формы, обработку ошибок и таймаут. Каждый пример можно скопировать в консоль браузера или в файл для Node.js, под кодом показан его вывод. Если слово «API» пока звучит туманно, начните с разбора что такое API простыми словами.

CheckroiCheckroiПодборка курсов по Javascript209 курсов • 37 школСравните цены, школы, программу и найдите выгодные предложения по обучениюСравнить→

Что такое fetch и что он возвращает

Рой отправляет запрос с ноутбука и получает ответ от сервера

Fetch API — встроенный в браузер интерфейс для сетевых запросов. Он пришёл на смену XMLHttpRequest, на котором раньше строили AJAX: страница получает данные с сервера без перезагрузки. Устанавливать ничего не нужно, fetch доступен во всех современных браузерах.

В Node.js функция тоже глобальная. По документации Node.js флаг --experimental-fetch перестал быть нужен в версии 18.0.0, а в 21.0.0 функция перестала считаться экспериментальной.

Как устроен результат вызова:

  • fetch() возвращает промис, данных в нём сразу нет. Проверка fetch(url) instanceof Promise даёт true.
  • Промис выполняется объектом Response, как только пришли заголовки ответа. Тело к этому моменту ещё не прочитано.
  • Тело читают методами json(), text(), blob(), и каждый из них тоже возвращает промис.

Ответ сервера чаще всего приходит в формате JSON. Как он устроен, мы разбирали в статье что такое JSON, а из чего состоят сам запрос и ответ, рассказано в материале что такое HTTP.

GET-запрос через fetch: пошаговая инструкция

Для примеров возьмём JSONPlaceholder, бесплатный тестовый API. По его руководству созданные и изменённые записи на сервере на самом деле не сохраняются, а ответ только имитирует успех, так что экспериментировать можно смело.

  1. Вызовите fetch с адресом ресурса. Метод по умолчанию GET, второй аргумент для него не нужен.
  2. Дождитесь ответа через await или .then().
  3. Проверьте response.ok: он равен true при статусах 200–299.
  4. Прочитайте тело нужным методом, для JSON это response.json().
  5. Оберните всё в try…catch, чтобы поймать сбой сети.

Вариант с async и await

const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
console.log(response.status, response.ok);

const todo = await response.json();
console.log(todo);

Вывод:

200 true
{ userId: 1, id: 1, title: 'delectus aut autem', completed: false }

Верхнеуровневый await работает в консоли браузера и в модулях (файл .mjs или type="module"). В обычном скрипте код нужно поместить внутрь async function.

Вариант с цепочкой then

fetch('https://jsonplaceholder.typicode.com/todos/1')
  .then(response => response.json())
  .then(todo => console.log(todo.title))
  .catch(error => console.error('Ошибка сети:', error));

Вывод: delectus aut autem. Запись через then встречается в старом коде и в примерах на форумах. Для нового кода удобнее async/await: ошибки ловятся обычным try…catch, а код читается сверху вниз.

Канал основателя Checkroi Вани Буявца3 700 человек читают мой Телеграм-канал про нейросетиСобрал промпты для Claude Code и ChatGPT, разборы ИИ-инструментов и лайфхаки по продвижению бизнеса в одном местеПрисоединиться

POST-запрос с JSON

Чтобы отправить данные, передайте вторым аргументом объект настроек. В нём нужны три поля: метод, заголовок с типом данных и само тело.

const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ title: 'Привет', body: 'Первый пост', userId: 1 }),
});

console.log(response.status, await response.json());

Вывод:

201 { title: 'Привет', body: 'Первый пост', userId: 1, id: 101 }

Статус 201 значит «создано», сервер вернул запись вместе с новым id. Так же отправляют PUT, PATCH и DELETE, меняется только method. Чем эти методы отличаются друг от друга, разобрано в статье методы HTTP: POST и GET, а почему повторный POST может создать дубль, объясняет материал об идемпотентности.

Объект в body без JSON.stringify не уйдёт как JSON. Fetch превратит его в строку [object Object], и сервер, который ждёт JSON, не сможет её разобрать.

Параметры запроса, формы и заголовки

Строка запроса через URLSearchParams

Параметры GET-запроса не склеивайте руками: кириллица и пробелы сломают адрес. URLSearchParams сам закодирует значения.

const params = new URLSearchParams({ userId: 1, completed: true });
const response = await fetch(`https://jsonplaceholder.typicode.com/todos?${params}`);
const todos = await response.json();

console.log(params.toString(), todos.length);

Вывод: userId=1&completed=true 11. Строка «курсы js» после кодирования выглядит так: q=%D0%BA%D1%83%D1%80%D1%81%D1%8B+js, и сервер получает её без искажений.

Отправка формы и файлов через FormData

const form = new FormData();
form.append('name', 'Вера');
form.append('file', fileInput.files[0]);

await fetch('/api/upload', { method: 'POST', body: form });

Заголовок Content-Type для FormData вручную не задавайте. Fetch сам поставит multipart/form-data вместе с параметром boundary, разделителем частей. Если прописать тип руками, разделитель потеряется и сервер не разберёт файл.

Заголовки запроса и ответа

Заголовки передают объектом в поле headers: токен авторизации, тип данных, язык. Заголовки ответа читают через response.headers.get():

const response = await fetch('https://jsonplaceholder.typicode.com/todos/1', {
  headers: { Accept: 'application/json', Authorization: 'Bearer ВАШ_ТОКЕН' },
});
console.log(response.headers.get('content-type'));
// application/json; charset=utf-8

Какие опции принимает fetch

Полный список опций описан в справочнике RequestInit на MDN. На практике чаще всего нужны эти:

Опция Для чего По умолчанию
method HTTP-метод: GET, POST, PUT, PATCH, DELETE GET
headers заголовки запроса нет
body тело: строка, FormData, URLSearchParams, Blob нет
credentials отправлять ли cookie: omit, same-origin, include same-origin
mode режим кросс-доменного запроса: cors, no-cors, same-origin cors
signal сигнал для отмены и таймаута нет
cache как использовать HTTP-кеш браузера default
redirect что делать с редиректом: follow, error, manual follow

Значения по умолчанию легко посмотреть самим: у пустого new Request(url) свойства method, credentials, mode, cache и redirect равны GET same-origin cors default follow.

Как читать ответ сервера

У объекта Response несколько методов для тела. Выбирают их по типу данных, который отдаёт сервер:

Метод Что вернёт Когда нужен
response.json() объект или массив ответ REST API
response.text() строку HTML, CSV, обычный текст
response.blob() Blob картинка, PDF, файл для скачивания
response.formData() FormData ответ в формате формы, встречается редко
response.arrayBuffer() двоичный буфер аудио, бинарные протоколы

Кроме тела пригодятся свойства status (код ответа), ok (успех или нет), headers и url, итоговый адрес после редиректов.

Тело читается один раз. Второй вызов упадёт: в Chrome с ошибкой body stream already read, в Node.js с Body is unusable: Body has already been read. Нужен и текст, и JSON? Прочитайте текст и разберите его через JSON.parse() или сделайте копию ответа методом response.clone() до чтения.

Полученный массив дальше обрабатывают обычными средствами языка. Например, новую запись из ответа можно дописать в локальный список, как в инструкции как добавить объект в массив JavaScript, и перерисовать страницу через DOM-дерево.

Обработка ошибок: почему 404 не попадает в catch

Две посылки на столе: пустая с красной биркой и целая с зелёной галочкой

Здесь fetch ведёт себя не так, как ждут новички. Руководство MDN по Fetch пишет так: промис отклоняется при некоторых ошибках, например сетевой или при неверной схеме адреса, а если сервер ответил ошибкой вроде 404, fetch() выполняется с объектом Response.

try {
  const response = await fetch('https://jsonplaceholder.typicode.com/todos/999999');
  console.log('дошли сюда, status', response.status, 'ok', response.ok);
} catch (error) {
  console.log('сюда не попадём');
}

Вывод: дошли сюда, status 404 ok false. Блок catch молчит, и код дальше пытается работать с пустым ответом.

Решение: проверять response.ok самим и бросать ошибку. Мы советуем сразу завести маленькую обёртку и звать её вместо голого fetch:

async function getJSON(url, options = {}) {
  const response = await fetch(url, options);
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }
  return response.json();
}

try {
  const todo = await getJSON('https://jsonplaceholder.typicode.com/todos/1');
  console.log(todo.title);
} catch (error) {
  console.error('Запрос не удался:', error.message);
}

Сетевой сбой (сервер выключен, нет интернета, домен не найден) отклонит промис с TypeError. Текст зависит от среды: в Chrome это Failed to fetch, в Node.js fetch failed. Поэтому в условиях удобнее проверять error.name, он одинаковый в обеих средах.

Ваня БуявецКанал основателя Checkroi Вани БуявцаЗабирайте промпты и обучение по нейросетям в моём Телеграм-каналеБольше 3 700 человек уже применяют Claude Code, ChatGPT и другие нейросети в работе, учёбе, бизнесе и жизниПерейти в канал

Таймаут и отмена запроса

Своего таймаута у fetch нет: медленный сервер может не отвечать минутами. Ограничение ставят через сигнал отмены.

Таймаут через статический метод AbortSignal

try {
  const response = await fetch('/api/slow', { signal: AbortSignal.timeout(5000) });
} catch (error) {
  if (error.name === 'TimeoutError') {
    console.log('Сервер не ответил за 5 секунд');
  }
}

По справке MDN сигнал по истечении времени отменяет запрос с ошибкой TimeoutError. Время считается активное: пока страница лежит в кеше возврата браузера (bfcache), отсчёт фактически стоит на паузе.

Ручная отмена через AbortController

Пригодится, когда пользователь ушёл со страницы или ввёл новый поисковый запрос, а старый ответ уже не нужен:

const controller = new AbortController();
const request = fetch('/api/search?q=js', { signal: controller.signal });

controller.abort(); // например, пользователь продолжил печатать

try {
  await request;
} catch (error) {
  console.log(error.name); // AbortError
}

Для поиска с подсказками так отменяют предыдущий запрос перед каждым новым. Иначе медленный старый ответ может прийти позже свежего и затереть результаты.

Несколько запросов параллельно

Независимые запросы не ждите по очереди. Promise.all запустит их одновременно и вернёт результаты в том же порядке:

const [user, todos] = await Promise.all([
  fetch('https://jsonplaceholder.typicode.com/users/1').then(r => r.json()),
  fetch('https://jsonplaceholder.typicode.com/todos?userId=1').then(r => r.json()),
]);

Если хотя бы один запрос упадёт, Promise.all отклонится целиком. Когда нужны все ответы, даже с ошибками, берите Promise.allSettled.

CORS и cookie в запросах fetch

Запрос на чужой домен браузер пропустит, только если сервер разрешил это заголовком Access-Control-Allow-Origin. Без разрешения fetch отклоняется с тем же TypeError: Failed to fetch, а причину видно только в консоли:

Access to fetch at 'https://www.iana.org/help/example-domains' from origin
'https://example.com' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested resource.

Что нужно знать про CORS:

  • Чинится на сервере, не во фронтенде. Сервер должен вернуть нужный заголовок, либо запрос идёт через свой бэкенд-прокси.
  • mode: 'no-cors' ошибку уберёт, но данные не отдаст. Ответ будет «непрозрачным»: type равен opaque, status равен 0, тело пустое.
  • В Node.js CORS не действует, это защита браузера. Поэтому запрос, который «работает в Node и не работает на сайте», почти всегда упирается в CORS.

Cookie на тот же домен fetch отправляет сам, это значение credentials: 'same-origin'. Для запросов на другой домен с сессией пользователя нужен credentials: 'include', и сервер тоже должен это разрешить. Почему без HTTPS такие запросы небезопасны, объясняет статья HTTP и HTTPS: в чём разница.

Частые ошибки при работе с fetch

Рой с чек-листом уверенно обходит грабли
Симптом Причина Что сделать
В консоли Promise {<pending>} вместо данных забыли await перед fetch или перед response.json() ждать оба промиса
Ошибка 404 или 500 не ловится в catch на ответ с ошибочным статусом fetch не отклоняется проверять response.ok и бросать ошибку
Сервер получил [object Object] объект передан в body без сериализации JSON.stringify() и заголовок Content-Type: application/json
Request with GET/HEAD method cannot have body тело передано в GET-запросе данные в строку запроса через URLSearchParams или метод POST
Сервер не видит загруженный файл Content-Type для FormData прописан вручную убрать заголовок, fetch поставит его сам
body stream already read тело прочитано второй раз читать один раз или вызвать response.clone()
Failed to fetch только в браузере CORS или смешанный контент HTTP на HTTPS-странице смотреть консоль, править заголовки на сервере

Fetch, XMLHttpRequest или axios

Три способа отправить запрос встречаются в проектах до сих пор. Выбор обычно такой:

fetch XMLHttpRequest axios
Что это встроенная функция встроенный объект, старый способ AJAX сторонняя библиотека из npm
Стиль кода промисы, async/await события и колбэки промисы, async/await
Ошибка на 404 нет, проверять ok нет, проверять status да, по умолчанию отклоняет всё вне 200–299
Когда брать новый код без зависимостей поддержка старого кода в проекте уже есть axios или нужны его перехватчики

Поведение axios на ошибочных статусах описано в документации axios: по умолчанию успешным считается статус от 200 до 299, условие меняют опцией validateStatus. Для учебных проектов и небольших сайтов хватает fetch с обёрткой вроде getJSON выше.

Где освоить JavaScript системно

Fetch закрывает одну задачу: забрать или отправить данные. Чтобы собрать из этого работающий интерфейс, нужны асинхронность, работа с DOM, обработка состояний загрузки и ошибок, а дальше React или Vue. Как выглядит этот путь целиком, мы расписали в статье как стать фронтенд-разработчиком, а чем занят такой специалист каждый день, рассказали в обзоре профессии JavaScript-разработчика.

Ниже курсы по JavaScript из нашего каталога, их можно сравнить по цене, длительности и формату:

КурсШколаСтоимость со скидкойВ рассрочкуДлитель­ностьОбзор курса от Checkroi
JavaScript
Перейти на сайт курса
SkillboxSkillbox65 744 ₽4086 ₽/мес.3 месяцаОбзор курса
Fullstack разработчик JavaScript
Перейти на сайт курса
OTUSOTUS75 000 ₽20 000 ₽/мес.6 месяцевОбзор курса
Факультет fullstack JavaScript
Перейти на сайт курса
GeekBrainsGeekBrains134 700 ₽3742 ₽/мес.15 месяцевОбзор курса
Fullstack-разработчик на JavaScript
Перейти на сайт курса
Академия ЭдюсонЭдюсон119 070 ₽4961 ₽/мес.11 месяцевОбзор курса
Фулстек-разработчик на JavaScript
Перейти на сайт курса
SkillboxSkillbox157 948 ₽4296 ₽/мес.11 месяцевОбзор курса

Больше программ — в полном каталоге курсов по Javascript

Часто задаваемые вопросы

Что возвращает fetch в JavaScript?

Промис. Когда приходят заголовки ответа, он выполняется объектом Response, а тело читают отдельно: await response.json() или response.text(). Как устроен формат ответа, рассказано в статье что такое JSON.

Как отправить POST-запрос через fetch?

Передайте вторым аргументом объект с method: 'POST', заголовком 'Content-Type': 'application/json' и телом JSON.stringify(данные). Без JSON.stringify сервер получит строку «[object Object]».

Почему fetch не выдаёт ошибку при статусе 404?

Ответ 404 или 500 fetch считает нормальным ответом: промис выполняется объектом Response, а отклоняется при сетевом сбое и похожих ошибках. Проверяйте response.ok и бросайте ошибку сами, если он равен false.

Как задать таймаут для fetch?

Передайте в опцию signal значение AbortSignal.timeout(5000). Если сервер не ответит за пять секунд, запрос отменится с ошибкой TimeoutError.

Работает ли fetch в Node.js?

Да, функция глобальная и не требует установки пакетов. Флаг --experimental-fetch не нужен с Node.js 18, а с версии 21 fetch больше не считается экспериментальным. Ограничений CORS в Node.js нет.

Чем fetch отличается от axios?

Fetch встроен в браузер и Node.js, axios ставится отдельно из npm. По умолчанию axios отклоняет промис на любом статусе вне 200–299, а у fetch статус нужно проверять вручную через response.ok.

Читайте Checkroi первым в Google
Добавьте Checkroi в избранные источники — и наши разборы курсов и обзоры школ будут показываться выше в вашей выдаче Google.
Оставить комментарий
0 комментариев
Форма комментария

Оставьте комментарий

Напишите, что думаете. Нам важно ваше мнение!