- Объяснять HTTP-запрос и ответ: метод, URL, заголовки, тело, код статуса
- Отправлять GET- и POST-запросы через
fetch, читать JSON и правильно обрабатывать ошибки HTTP и сети - Строить надёжные асинхронные приёмы: тайм-ауты, повторные попытки, параллельные запросы и
URLSearchParams
Прогноз погоды в приложении, курсы валют, лента в соцсети — всё это приходит с серверов через API. Твой код отправляет HTTP-запрос, а сервер отвечает данными, обычно в формате JSON. fetch — современный встроенный инструмент для этого в браузерах и в Node.js (начиная с версии 18).
HTTP за одну минуту
Запрос состоит из метода, URL, заголовков (headers) и иногда тела (body). В ответе есть код статуса, заголовки и тело. Первая цифра кода статуса задаёт смысл: 2xx — успех, 3xx — перенаправление, 4xx — ошибка клиента (неверный запрос), 5xx — ошибка сервера.
POST /api/orders HTTP/1.1
Host: shop.example.com
Content-Type: application/json
Authorization: Bearer <token>
{"productId": 7, "quantity": 2}
HTTP/1.1 201 Created
Content-Type: application/json
{"id": 1043, "productId": 7, "quantity": 2, "status": "new"}Стиль построения API: данные — это ресурсы со своими URL (/users, /users/42), а действия выражаются методами HTTP. Каждый запрос независим (сервер «не помнит» предыдущий), а данные обычно передаются в формате JSON.
| Метод | Смысл | Пример и успешный ответ |
|---|---|---|
| GET | прочитать | GET /users/42 → 200 OK |
| POST | создать | POST /orders → 201 Created |
| PUT / PATCH | заменить / частично обновить | PATCH /users/42 → 200 OK |
| DELETE | удалить | DELETE /orders/1043 → 204 No Content |
GET через fetch
fetch(url) возвращает промис, который выполняется с объектом Response. Работа идёт в два шага: сначала ждёшь ответ (приходят заголовки), затем читаешь тело через response.json() — это тоже промис. Главное: fetch отклоняется только при сетевой ошибке (нет интернета, неизвестный домен, CORS). Ответы 404 и 500 тоже «успешно приходят», поэтому всегда проверяй response.ok (статус 200–299).
async function getUser(id) {
const response = await fetch(`https://api.example.com/users/${id}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
}
try {
const user = await getUser(42);
console.log(user.name, user.city);
await getUser(999999);
} catch (error) {
console.log('Request failed:', error.message);
}Aysel Baku Request failed: HTTP 404
api.example.com — условный адрес: у настоящего API поля будут называться иначе. Запускатель на этой странице не отправляет настоящих запросов, поэтому ниже мы сымитируем сервер.Объект Response можно создать прямо в JavaScript. Это позволяет сымитировать сервер и тренироваться без интернета: fakeFetch возвращает Response, как настоящий fetch, а твой код не замечает разницы. В тестах поступают именно так.
const db = { 42: { id: 42, name: 'Aysel', city: 'Baku' } };
async function fakeFetch(url) {
await new Promise((resolve) => setTimeout(resolve, 50));
const id = Number(url.split('/').pop());
const user = db[id];
return new Response(JSON.stringify(user ?? { error: 'Not found' }), {
status: user ? 200 : 404,
headers: { 'Content-Type': 'application/json' },
});
}
for (const id of [42, 7]) {
const response = await fakeFetch(`/api/users/${id}`);
console.log(response.status, response.ok, response.headers.get('content-type'));
console.log(await response.json());
}▸ Ожидаемый результат
200 true application/json
{ id: 42, name: 'Aysel', city: 'Baku' }
404 false application/json
{ error: 'Not found' }POST, заголовки и ошибки
Чтобы отправить данные, передай в fetch вторым аргументом объект настроек: method, headers и body. Тело должно быть текстом, поэтому объект превращают в строку через JSON.stringify, а заголовок Content-Type: application/json сообщает серверу формат. API, требующие входа, обычно ждут заголовок Authorization: Bearer <token>.
async function createOrder(order, token) {
const response = await fetch('https://api.example.com/orders', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(order),
});
if (response.status === 401) throw new Error('Please log in again');
if (!response.ok) throw new Error(`Server error ${response.status}`);
return response.json();
}
const saved = await createOrder({ productId: 7, quantity: 2 }, token);
console.log('Created order', saved.id, saved.status);Created order 1043 new
Надёжные асинхронные приёмы
Настоящая сеть медленная и ненадёжная. Помогают четыре привычки: собирай параметры запроса через URL и URLSearchParams (спецсимволы кодируются автоматически); отправляй независимые запросы параллельно через Promise.all; ставь ограничение по времени — fetch(url, { signal: AbortSignal.timeout(8000) }) отменит запрос через 8 секунд с ошибкой TimeoutError; при временных сбоях (5xx, 429) повторяй попытку с растущей паузой.
const url = new URL('https://api.example.com/search');
url.searchParams.set('q', 'plov & dolma');
url.searchParams.set('city', 'Baku');
url.searchParams.set('page', 2);
console.log(url.toString());
console.log(url.searchParams.get('q'));
console.log(Object.fromEntries(url.searchParams));▸ Ожидаемый результат
https://api.example.com/search?q=plov+%26+dolma&city=Baku&page=2
plov & dolma
{ q: 'plov & dolma', city: 'Baku', page: '2' }?q=plov & dolma сломал бы запрос: & был бы принят за новый параметр. URLSearchParams кодирует его как %26.function slowServer(ms, signal) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => resolve('data'), ms);
signal.addEventListener('abort', () => {
clearTimeout(timer);
reject(new DOMException('Request took too long', 'TimeoutError'));
});
});
}
async function withTimeout(ms, limit) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), limit);
try {
return await slowServer(ms, controller.signal);
} catch (error) {
return `failed: ${error.name}`;
} finally {
clearTimeout(timer);
}
}
console.log(await withTimeout(50, 200));
console.log(await withTimeout(500, 200));▸ Ожидаемый результат
data failed: TimeoutError
AbortController создаёт сигнал отмены; настоящий fetch принимает этот signal. AbortSignal.timeout(ms) делает то же самое в одну строку.Напиши помощника getJSON(url): он вызывает fakeFetch(url), при неуспешном ответе выбрасывает ошибку с сообщением HTTP <status>, а иначе возвращает разобранный JSON.
const products = { 1: { id: 1, title: 'Notebook', price: 5 } };
async function fakeFetch(url) {
const id = Number(url.split('/').pop());
const product = products[id];
return new Response(JSON.stringify(product ?? { error: 'Not found' }), {
status: product ? 200 : 404,
headers: { 'Content-Type': 'application/json' },
});
}
async function getJSON(url) {
// call fakeFetch, check response.ok, return the JSON
}
for (const url of ['/api/products/1', '/api/products/99']) {
try {
console.log(await getJSON(url));
} catch (error) {
console.log('Error:', error.message);
}
}▸ Ожидаемый результат
{ id: 1, title: 'Notebook', price: 5 }
Error: HTTP 404flakyFetch первые два раза возвращает 503. Напиши fetchWithRetry(url, retries): после каждой неудачной попытки она выводит Attempt N: 503, при ошибке 5xx ждёт 100, затем 200 мс (каждый раз вдвое дольше) и пробует снова, а когда повторы retries закончились или пришёл 4xx — выбрасывает HTTP <status>.
let calls = 0;
async function flakyFetch(url) {
calls++;
const ok = calls >= 3;
return new Response(JSON.stringify(ok ? { rates: { USD: 1.7 } } : {}), { status: ok ? 200 : 503 });
}
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function fetchWithRetry(url, retries) {
// only one attempt for now: add retries with a growing delay
const response = await flakyFetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
}
const data = await fetchWithRetry('/api/rates', 3);
console.log('Rates:', data.rates);▸ Ожидаемый результат
Attempt 1: 503
Attempt 2: 503
Rates: { USD: 1.7 }Главное
- HTTP: запрос = метод + URL + заголовки + тело; ответ = статус + заголовки + тело. 2xx — успех, 4xx — ошибка клиента, 5xx — ошибка сервера.
- REST: ресурсы живут по URL, а действия выражаются методами: GET читает, POST создаёт, PUT/PATCH обновляют, DELETE удаляет.
fetchотклоняется только при сетевых сбоях — всегда проверяйresponse.okи читай тело один раз черезawait response.json().- Для POST передавай
method, заголовокContent-Type: application/jsonиbody: JSON.stringify(data). - Надёжность: ограничение по времени (
AbortSignal.timeout), повторы при временных ошибках 5xx,Promise.allдля параллельных запросов иURLSearchParamsдля параметров.
Проверь себя
Вопросов: 10. Каждый правильный ответ приносит XP.
fetch отправлен на адрес, который возвращает 404. Что произойдёт?