fetch() отправляет сетевой запрос и возвращает промис с ответом от сервера.
Чаще всего fetch() используют, когда нужно:
- загрузить данные из API
- отправить форму или JSON на сервер
- получить текст, HTML, файл или изображение
- проверить статус ответа
- работать с запросами через
async/await
Главная мысль: fetch() не возвращает сразу готовые данные. Сначала он возвращает объект Response, а уже из него нужно отдельно прочитать тело ответа.
Синтаксис
Базовая запись выглядит так:
fetch(url, options)
Где:
url- адрес запросаoptions- необязательный объект с настройками запроса
Самый простой запрос можно написать так:
fetch("/api/users")
Если нужны метод, заголовки или тело запроса, передают второй аргумент:
fetch("/api/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ name: "Анна" }),
});
Что возвращает fetch()
fetch() всегда возвращает Promise.
Если запрос удалось выполнить на сетевом уровне, этот промис успешно завершится объектом Response.
const promise = fetch("/api/users");
console.log(promise instanceof Promise); // true
Чтобы получить сам ответ, нужно использовать then() или await.
fetch("/api/users").then((response) => {
console.log(response); // Response
});
Объект Response содержит статус, заголовки и методы для чтения тела ответа.
Простой пример с JSON
Чаще всего сервер возвращает JSON.
fetch("/api/users")
.then((response) => response.json())
.then((users) => {
console.log(users);
});
Здесь происходит два асинхронных шага:
fetch()ждёт ответ сервераresponse.json()читает тело ответа и превращает JSON в значение JavaScript
response.json() тоже возвращает промис, поэтому его нужно дождаться.
Пример с async/await
С async/await код обычно читается проще.
async function loadUsers() {
const response = await fetch("/api/users");
const users = await response.json();
console.log(users);
}
loadUsers();
Такой вариант удобен, когда нужно последовательно выполнить запрос, проверить ответ и затем обработать данные.
fetch() не читает тело ответа автоматически
Это один из главных нюансов.
После await fetch() у вас есть только объект Response, а не готовые данные.
async function loadUser() {
const response = await fetch("/api/user");
console.log(response); // Response
}
Чтобы получить данные, нужно вызвать подходящий метод:
async function loadUser() {
const response = await fetch("/api/user");
const user = await response.json();
console.log(user.name);
}
Для JSON используют response.json(), для обычного текста - response.text().
Как читать разные типы ответа
У объекта Response есть несколько методов для чтения тела.
const response = await fetch("/api/data");
const data = await response.json();
Основные варианты:
response.json()- прочитать JSON и превратить его в значение JavaScriptresponse.text()- прочитать обычный текстresponse.blob()- прочитать файл или изображение какBlobresponse.formData()- прочитать данные формыresponse.arrayBuffer()- прочитать бинарные данные
Пример с текстом:
const response = await fetch("/about.html");
const html = await response.text();
console.log(html);
Важно: тело ответа можно прочитать только один раз.
const response = await fetch("/api/user");
const user = await response.json();
const text = await response.text(); // ошибка: тело уже прочитано
Если нужно прочитать ответ двумя разными способами, обычно проблема в логике обработки. На практике заранее выбирают один формат.
Статус ответа
Объект Response позволяет узнать HTTP-статус.
const response = await fetch("/api/users");
console.log(response.status); // например, 200
console.log(response.statusText); // например, "OK"
console.log(response.ok); // true или false
Свойство response.ok равно true, если статус находится в диапазоне от 200 до 299.
async function loadUsers() {
const response = await fetch("/api/users");
if (response.ok) {
const users = await response.json();
console.log(users);
} else {
console.log("Сервер вернул ошибку:", response.status);
}
}
Это самая частая проверка после запроса.
HTTP-ошибки не попадают в catch автоматически
Это очень важный момент.
Если сервер ответил 404 или 500, fetch() обычно не считает это ошибкой промиса. Запрос был выполнен, сервер ответил, поэтому промис успешно завершится объектом Response.
try {
const response = await fetch("/api/unknown-page");
console.log(response.status); // 404
console.log(response.ok); // false
} catch (error) {
console.log("Сюда код обычно не попадёт из-за 404");
}
catch сработает, например, при сетевой ошибке, проблеме CORS или отмене запроса.
Поэтому HTTP-статус лучше проверять вручную:
async function loadUsers() {
const response = await fetch("/api/users");
if (!response.ok) {
throw new Error(`Ошибка HTTP: ${response.status}`);
}
const users = await response.json();
return users;
}
Теперь неуспешный HTTP-статус можно обработать через catch.
loadUsers()
.then((users) => {
console.log(users);
})
.catch((error) => {
console.log(error.message);
});
Обработка ошибок через try…catch
С async/await ошибки обычно обрабатывают через try...catch.
async function loadUsers() {
try {
const response = await fetch("/api/users");
if (!response.ok) {
throw new Error(`Ошибка HTTP: ${response.status}`);
}
const users = await response.json();
console.log(users);
} catch (error) {
console.log("Не удалось загрузить пользователей");
console.log(error.message);
}
}
В такой обработчик попадут:
- сетевые ошибки
- отмена запроса
- ошибка, которую вы выбросили вручную при
!response.ok - ошибка чтения JSON, если тело ответа оказалось невалидным JSON
GET-запрос с параметрами
Для параметров удобно использовать URL и searchParams.
const url = new URL("/api/products", window.location.origin);
url.searchParams.set("category", "books");
url.searchParams.set("limit", "10");
const response = await fetch(url);
const products = await response.json();
console.log(products);
В итоге запрос уйдёт примерно на такой адрес:
/api/products?category=books&limit=10
Такой способ лучше ручной склейки строк, потому что URLSearchParams корректно кодирует специальные символы.
const url = new URL("/api/search", window.location.origin);
url.searchParams.set("q", "javascript уроки");
console.log(url.toString());
Пробелы и другие символы будут подготовлены для URL автоматически.
POST-запрос с JSON
Чтобы отправить данные на сервер, обычно указывают method, headers и body.
async function createUser() {
const response = await fetch("/api/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Анна",
age: 25,
}),
});
if (!response.ok) {
throw new Error(`Ошибка HTTP: ${response.status}`);
}
const createdUser = await response.json();
console.log(createdUser);
}
Объект JavaScript нельзя просто положить в body. Его нужно превратить в строку, например через JSON.stringify().
body: JSON.stringify({ name: "Анна" })
Заголовок Content-Type помогает серверу понять, что тело запроса содержит JSON.
Отправка формы через FormData
Если данные пришли из формы, можно использовать FormData.
const form = document.querySelector("form");
form.addEventListener("submit", async (event) => {
event.preventDefault();
const formData = new FormData(form);
const response = await fetch("/api/profile", {
method: "POST",
body: formData,
});
if (!response.ok) {
console.log("Ошибка отправки формы");
return;
}
console.log("Форма отправлена");
});
Для FormData обычно не нужно вручную ставить Content-Type. Браузер сам добавит правильный заголовок с нужной границей для multipart-данных.
Заголовки ответа
Заголовки ответа доступны через response.headers.
const response = await fetch("/api/users");
console.log(response.headers.get("content-type"));
Например, можно проверить, что сервер действительно вернул JSON.
const response = await fetch("/api/users");
const contentType = response.headers.get("content-type");
if (contentType && contentType.includes("application/json")) {
const users = await response.json();
console.log(users);
} else {
console.log("Сервер вернул не JSON");
}
Не все заголовки доступны JavaScript-коду в браузере. Некоторые ограничены правилами безопасности и CORS.
Отправка заголовков запроса
Заголовки запроса передают в options.headers.
const response = await fetch("/api/profile", {
headers: {
Authorization: "Bearer token",
Accept: "application/json",
},
});
Это часто используется для токенов, форматов ответа и других служебных данных.
Но не все заголовки можно выставлять вручную. Часть заголовков браузер контролирует сам.
credentials и cookies
По умолчанию fetch() в браузере отправляет cookies для запросов на тот же сайт.
Если нужно явно указать поведение, используют настройку credentials.
fetch("/api/profile", {
credentials: "same-origin",
});
Основные варианты:
"same-origin"- отправлять cookies только на тот же origin"include"- отправлять cookies даже для кросс-доменных запросов, если сервер это разрешает"omit"- не отправлять cookies
Для запросов на другой домен одного credentials: "include" недостаточно. Сервер тоже должен разрешить такой запрос через CORS.
Отмена запроса через AbortController
fetch() можно отменить с помощью AbortController.
const controller = new AbortController();
fetch("/api/users", {
signal: controller.signal,
})
.then((response) => response.json())
.then((users) => {
console.log(users);
})
.catch((error) => {
console.log(error.name); // "AbortError"
});
controller.abort();
На практике это полезно, когда пользователь ушёл со страницы, изменил поисковый запрос или запрос выполняется слишком долго.
Пример с простым таймаутом:
async function fetchWithTimeout(url, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => {
controller.abort();
}, timeout);
try {
const response = await fetch(url, {
signal: controller.signal,
});
return response;
} finally {
clearTimeout(timeoutId);
}
}
У fetch() нет обычной настройки timeout, поэтому отмену часто делают именно через AbortController.
Несколько запросов параллельно
Так как fetch() возвращает промис, его удобно использовать вместе с Promise.all().
async function loadPageData() {
const [userResponse, settingsResponse] = await Promise.all([
fetch("/api/user"),
fetch("/api/settings"),
]);
if (!userResponse.ok || !settingsResponse.ok) {
throw new Error("Не удалось загрузить данные страницы");
}
const [user, settings] = await Promise.all([
userResponse.json(),
settingsResponse.json(),
]);
console.log(user, settings);
}
Такой подход полезен, когда запросы не зависят друг от друга и их можно запускать одновременно.
fetch() и CORS
fetch() подчиняется правилам безопасности браузера.
Если страница открыта на одном домене, а запрос идёт на другой домен, браузер проверяет CORS-заголовки сервера.
fetch("https://api.learn-javascript.ru/data")
Если сервер не разрешил такой запрос, JavaScript-код не сможет получить ответ.
Важно: fetch() не умеет обходить CORS. Разрешение должно быть настроено на стороне сервера.
Когда это полезно
fetch() используют почти во всех задачах, где фронтенду нужно общаться с сервером:
- загрузка списка товаров
- отправка формы обратной связи
- авторизация пользователя
- сохранение настроек
- поиск с параметрами в URL
- загрузка HTML, текста или файлов
Пример поиска:
async function searchProducts(query) {
const url = new URL("/api/search", window.location.origin);
url.searchParams.set("q", query);
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Ошибка HTTP: ${response.status}`);
}
return await response.json();
}
Частые ошибки
Думать, что fetch() сразу возвращает данные
const users = fetch("/api/users");
console.log(users); // Promise
Нужно дождаться промиса и отдельно прочитать тело ответа.
const response = await fetch("/api/users");
const users = await response.json();
Забывать await перед response.json()
const response = await fetch("/api/users");
const users = response.json();
console.log(users); // Promise
response.json() тоже асинхронный метод.
Правильно:
const response = await fetch("/api/users");
const users = await response.json();
console.log(users);
Ожидать, что 404 попадёт в catch автоматически
try {
const response = await fetch("/api/missing");
console.log(response.status); // 404
} catch (error) {
console.log("Ошибка");
}
Для HTTP-статусов проверяйте response.ok.
const response = await fetch("/api/missing");
if (!response.ok) {
throw new Error(`Ошибка HTTP: ${response.status}`);
}
Читать тело ответа два раза
const response = await fetch("/api/user");
const user = await response.json();
const text = await response.text(); // ошибка
Тело ответа - это поток данных. После чтения оно считается использованным.
Отправлять объект в body без JSON.stringify()
fetch("/api/users", {
method: "POST",
body: { name: "Анна" }, // ошибка логики
});
Обычно нужно так:
fetch("/api/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ name: "Анна" }),
});
Передавать body в GET-запрос
Для GET данные обычно передают через параметры URL.
const url = new URL("/api/search", window.location.origin);
url.searchParams.set("q", "javascript");
fetch(url);
Если нужно отправить тело запроса, обычно используют POST, PUT или PATCH.
Пытаться обойти CORS на фронтенде
fetch("https://api.learn-javascript.ru/data");
Если сервер не разрешает запросы с вашего сайта, браузер заблокирует доступ к ответу. Это не исправляется специальной настройкой fetch() на фронтенде.
Быстрая памятка
fetch()отправляет HTTP-запрос- функция возвращает
Promise - результатом успешного промиса будет объект
Response - тело ответа нужно читать отдельно через
json(),text()и другие методы response.json()тоже возвращает промис- HTTP-статусы
404и500сами по себе не переводят промис вcatch - для проверки HTTP-ошибок используйте
response.ok - объект в
bodyобычно нужно превращать в JSON черезJSON.stringify() - тело ответа можно прочитать только один раз
- отмена запроса делается через
AbortController fetch()не обходит CORS-ограничения браузера
Коротко
fetch() - это встроенная функция JavaScript для сетевых запросов. Она возвращает промис с объектом Response, а данные из ответа нужно читать отдельно. В реальном коде важно проверять response.ok, помнить, что response.json() тоже асинхронный, правильно отправлять headers и body, а для отмены долгих или уже ненужных запросов использовать AbortController.