Справочник · JavaScript
Fetch API: получаем и отправляем данные
fetch() — встроенная функция браузера для HTTP-запросов. Она принимает адрес и необязательные параметры, а возвращает Promise с объектом Response. Из ответа данные читают методами json(), text() или blob() — они тоже асинхронные. Так страница получает данные с сервера без перезагрузки.
const res = await fetch(url), затемawait res.json().- Ответ 404 или 500 — не ошибка для fetch: проверяй
res.okсам. - POST с JSON:
method, заголовокContent-TypeиJSON.stringify. - FormData отправляй без заголовка Content-Type — браузер поставит его сам.
- Таймаут и отмена — через
AbortControllerилиAbortSignal.timeout().
Минимальный пример: GET и JSON
async function loadPosts() {
const response = await fetch('/api/posts');
if (!response.ok) {
throw new Error(`Ошибка ${response.status}`);
}
return response.json();
}
loadPosts()
.then((posts) => console.log(posts))
.catch((error) => console.error(error.message));Два ожидания: первое — пока придут заголовки ответа, второе — пока тело прочитается и разберётся как JSON. Функция с await внутри должна быть объявлена как async.
Объект Response
| Свойство или метод | Что даёт |
|---|---|
response.ok | true, если статус от 200 до 299 |
response.status | код ответа: 200, 404, 500 |
response.headers.get('content-type') | заголовок ответа |
await response.json() | тело, разобранное как JSON |
await response.text() | тело строкой |
await response.blob() | тело как файл: картинка, PDF |
Тело можно прочитать только один раз: после json() вызов text() выбросит ошибку. Если нужно и то и другое, прочитай текст и разбери его через JSON.parse.
Параметры запроса
| Параметр | Значение |
|---|---|
method | 'GET' по умолчанию, 'POST', 'PUT', 'PATCH', 'DELETE' |
headers | объект заголовков: { 'Content-Type': 'application/json' } |
body | тело: строка JSON, FormData, URLSearchParams, Blob |
signal | сигнал для отмены запроса |
credentials | 'include' — отправлять cookie на чужой домен |
cache | 'no-store' — не брать ответ из кеша |
Параметры в адресе
const params = new URLSearchParams({ q: 'flex box', page: 2 });
const response = await fetch(`/api/search?${params}`);
// /api/search?q=flex+box&page=2URLSearchParams сам экранирует пробелы, кириллицу и символы вроде &. Склеивать адрес вручную через + опасно: запрос «C&A» сломает строку параметров.
POST: отправка JSON
const response = await fetch('/api/comments', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: 'Спасибо за урок', lessonId: 12 }),
});
const saved = await response.json();Объект нельзя передать в body напрямую — его превращают в строку через JSON.stringify. Заголовок Content-Type говорит серверу, что внутри JSON. Сервер должен ожидать именно этот формат.
Отправка формы через FormData
form.addEventListener('submit', async (event) => {
event.preventDefault();
const response = await fetch(form.action, {
method: 'POST',
body: new FormData(form), // включая файлы из input type="file"
});
});С FormData заголовок Content-Type писать не нужно: браузер сам поставит multipart/form-data с правильной границей между полями. Если указать его вручную, сервер не сможет разобрать тело.
Ошибки: сеть и статус
async function getJSON(url) {
let response;
try {
response = await fetch(url);
} catch (error) {
throw new Error('Нет связи с сервером. Проверь интернет и повтори.');
}
if (!response.ok) {
throw new Error(`Сервер ответил ${response.status}`);
}
return response.json();
}Promise от fetch отклоняется только при сетевом сбое, блокировке CORS или отмене. Ответ 404 или 500 — это успешно полученный ответ, и он не попадёт в catch. Поэтому проверка response.ok обязательна в каждом запросе.
Таймаут и отмена запроса
// отменить вручную
const controller = new AbortController();
fetch('/api/search?q=css', { signal: controller.signal });
controller.abort();
// отменить, если ответа нет 5 секунд
const response = await fetch('/api/data', { signal: AbortSignal.timeout(5000) });Отмена нужна в живом поиске: человек ввёл новый запрос, и старый, медленный ответ не должен приехать позже и затереть новый. При отмене fetch выбрасывает ошибку с именем AbortError (при таймауте — TimeoutError), её обычно не показывают пользователю.
CORS: почему запрос к чужому сайту блокируется
Браузер разрешает читать ответ с другого домена, только если сервер явно это позволил заголовком Access-Control-Allow-Origin. Если заголовка нет, в консоли будет ошибка CORS, а fetch отклонится. Исправить это можно только на сервере или через свой прокси — параметр mode: 'no-cors' не помогает: он даёт пустой непрозрачный ответ, который нельзя прочитать.
Что показать человеку
У каждого запроса четыре состояния, и для каждого нужен свой вид: загрузка (кнопка неактивна, текст «Загружаем…»), успех, пустой результат («Пока ничего нет») и ошибка (что случилось и кнопка «Повторить»). Пустой список и ошибка — разные ситуации, не показывай их одинаково.
fetch или XMLHttpRequest
Современный API на промисах, работает с async/await, поддерживает отмену и потоки. Используй его.
Старый API на колбэках. Встречается в старом коде; его единственное преимущество — простое событие прогресса загрузки файла.
Частые ошибки
- Не проверяют
response.okи пытаются разобрать страницу ошибки 404 как JSON. - Забывают
awaitпередresponse.json()и получают Promise вместо данных. - Отправляют объект в body без
JSON.stringify. - Ставят Content-Type вручную при отправке FormData.
- Оставляют кнопку активной во время запроса, и человек отправляет форму несколько раз.
Доступность
Пока идёт запрос, сообщай об этом не только спиннером: добавь текст «Загружаем…» в область с aria-live="polite", а у кнопки — атрибут disabled или aria-busy="true" у контейнера. Ошибку выводи текстом рядом с действием и давай способ повторить.
Поддержка браузерами
fetch работает во всех актуальных браузерах и в Node.js начиная с 18-й версии. AbortController поддерживается везде, AbortSignal.timeout() — в браузерах с 2022 года. Internet Explorer fetch не поддерживает.
Практика
Загрузи список постов с https://jsonplaceholder.typicode.com/posts?_limit=5 и выведи заголовки списком. Пока данные едут, показывай «Загружаем…», при ошибке — сообщение и кнопку «Повторить». Если ответа нет 5 секунд, отменяй запрос.
Показать решение
const status = document.querySelector('.status');
const list = document.querySelector('.posts');
async function load() {
status.textContent = 'Загружаем…';
try {
const res = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=5', {
signal: AbortSignal.timeout(5000),
});
if (!res.ok) throw new Error(`Ошибка ${res.status}`);
const posts = await res.json();
list.innerHTML = '';
for (const post of posts) {
const li = document.createElement('li');
li.textContent = post.title;
list.append(li);
}
status.textContent = '';
} catch (error) {
status.textContent = 'Не удалось загрузить посты.';
}
}
load();
document.querySelector('.retry').addEventListener('click', load);Частые вопросы
Почему fetch не попадает в catch при ошибке 404?
Для fetch ответ 404 — успешно полученный ответ сервера. catch ловит только сетевые сбои, CORS и отмену. Проверяй response.ok и выбрасывай ошибку сам.
Как отправить POST-запрос с JSON?
Передай method: 'POST', заголовок 'Content-Type': 'application/json' и body: JSON.stringify(данные).
Как сделать таймаут для fetch?
Передай signal: AbortSignal.timeout(5000) — запрос отменится через 5 секунд. В старых браузерах — AbortController и setTimeout с вызовом abort().
Что значит ошибка CORS?
Сервер другого домена не разрешил читать свой ответ. Решается настройкой заголовка Access-Control-Allow-Origin на сервере или запросом через свой сервер.
Почему response.json() возвращает Promise?
Тело ответа может приходить частями, и браузер читает его асинхронно. Поэтому перед response.json() тоже нужен await.
Источники
Практикумы по теме
- Индикатор, пока данные в пути — Пока fetch не вернулся, повторные клики легко отправить тот же запрос несколько раз.
- Что показать, если сеть не ответила — Сервер может не ответить. Страница при этом обязана сказать, что случилось, и оставить способ повторить.