Курс сомони в приложении банка, погода в Душанбе, список товаров в интернет-магазине, лента новостей: всё это лежит не в вашем браузере, а на чужих компьютерах — серверах. Чтобы забрать оттуда данные, страница отправляет HTTP-запрос и ждёт ответ. Сегодня вы научитесь делать это на чистом JavaScript через fetch — и собирать собственную обёртку api.js, которой будете пользоваться в каждом следующем проекте.
Объяснять, из чего состоят HTTP-запрос и ответ: метод, URL, заголовки, тело, статус
Различать методы GET, POST, PUT, PATCH, DELETE и группы кодов статуса 2xx / 3xx / 4xx / 5xx
Делать запросы через fetch и читать ответ: response.json(), status, ok, headers
Отправлять данные методом POST: заголовок Content-Type и JSON.stringify
Отличать сетевую ошибку от ответа «404» и обрабатывать обе через response.ok и try / catch
Собрать обёртку api.js и проверить код, который написал ИИ, на ошибочных ответах
Как работать с этой страницей
Код на тёмном фоне с кнопкой ▶ Запустить — живой. В этом уроке многие примеры ходят в интернет (к учебным серверам jsonplaceholder и dummyjson): подождите секунду-две, пока ответ приедет и консоль допишет строки. Если интернета нет, запросы упадут — и это тоже полезный опыт: ниже мы научимся такие ситуации обрабатывать. Часть задач работает без сети (там мы сами «подделываем» ответы сервера) — они проверяются мгновенно. Подсказки спрятаны под замочком 🔒.
01 HTTP: как браузер разговаривает с сервером
Клиент — это ваш браузер (или ваш код в нём). Сервер — компьютер, на котором лежат данные. Они общаются по правилам протокола HTTP: клиент шлёт запрос (request), сервер возвращает ответ (response). Всегда по очереди: сервер сам первым не пишет.
Аналогия из жизни
Вы в кафе у Рудаки заказываете через официанта. Запрос: «Принесите (метод) плов (адрес блюда в меню), без моркови (заголовок с пожеланиями)». Ответ: официант возвращается и сначала говорит статус — «Принёс» (200) или «Такого блюда нет» (404) или «Повар заболел» (500), потом ставит на стол тело ответа — сам плов.
Из чего состоит адрес
Адрес ресурса называется URL. В нём несколько частей, и браузер умеет их разбирать с помощью встроенного класса URL.
Запрос состоит из четырёх частей, ответ — из трёх. Запомните эту схему: она нужна, чтобы понимать любую документацию к API.
Запрос (клиент → сервер)
Метод (что делать) · URL (где) · заголовки (служебная информация) · тело (данные, если отправляем).
Ответ (сервер → клиент)
Статус (число: как всё прошло) · заголовки (например, тип данных) · тело (сами данные, чаще всего JSON).
Методы: глагол запроса
Метод говорит серверу, что мы хотим сделать. Для работы с данными есть пять основных. Адрес у них может быть один и тот же — отличается только глагол.
Метод
Что делает
Пример из жизни
Тело запроса
GET
Получить данные
Посмотреть меню
нет
POST
Создать новую запись
Оформить новый заказ
да
PUT
Заменить запись целиком
Переписать заказ заново
да
PATCH
Изменить часть записи
Поменять только адрес доставки
да
DELETE
Удалить запись
Отменить заказ
нет
Если метод не указан, браузер использует GET. Сегодня мы почти всё время будем получать данные, а к остальным методам придём в разделе 05.
Коды статуса: число, которое сервер говорит первым
Первая цифра кода — это группа. Если запомнить группы, то 90% кодов вы поймёте без справочника.
Код
Группа
Смысл
Частые примеры
2xx
Успех
Всё получилось
200 OK, 201 Created (создано), 204 No Content (успех, но тела нет)
3xx
Перенаправление
Ищите в другом месте
301 страница переехала, 304 ничего не изменилось
4xx
Ошибка клиента
Вы что-то сделали не так
400 плохой запрос, 401 не вошли, 403 нет прав, 404 не найдено, 429 слишком часто
5xx
Ошибка сервера
Сломалось у них
500 внутренняя ошибка, 503 сервер недоступен
Заголовки: служебные записки
Content-Type
Какого типа тело. Для JSON это application/json. Есть и у запроса (что мы отправляем), и у ответа (что получили).
Accept
В запросе: «какой формат ответа я понимаю».
Authorization
В запросе: ключ или токен, подтверждающий, кто вы. Без него закрытые API ответят 401. В этом уроке он не нужен — мы берём открытые API.
JSON: язык, на котором говорят API
Данные по сети ходят как текст. Объекты и массивы превращают в текст формата JSON и обратно: JSON.stringify(объект) → строка, JSON.parse(строка) → объект. Правила строже, чем в JS: ключи и строки только в двойных кавычках, никаких комментариев и запятых в конце.
Туда и обратно · Ctrl + Enter
const student = { name: "Мадина", age: 17, courses: ["JS", "CSS"] };
const text = JSON.stringify(student);
console.log(text); // это строка
console.log(typeof text);
const back = JSON.parse(text);
console.log(back.name, back.courses.length);
Частая ошибка
Писать JSON «как в JavaScript»: без кавычек у ключей или с одинарными кавычками. Такой текст JSON.parse не принимает и бросает SyntaxError.
Этот код выдаст ошибку · Ctrl + Enter
const text = "{ name: 'Мадина' }"; // ✖ ключ без кавычек, строка в одинарныхconst student = JSON.parse(text);
console.log(student.name);
Допишите функцию describeStatus(code): она возвращает «успех» для 200–299, «перенаправление» для 300–399, «ошибка клиента» для 400–499, «ошибка сервера» для 500–599 и «неизвестно» для всего остального.
Можно редактировать · Ctrl + Enter
functiondescribeStatus(code) {
// ваш код
}
for (const code of [200, 201, 301, 404, 500, 99]) {
console.log(code + ": " + describeStatus(code));
}
Ожидаемый результат
200: успех
201: успех
301: перенаправление
404: ошибка клиента
500: ошибка сервера
99: неизвестно
Подсказка 1
Используйте цепочку if … else if с неравенствами: code >= 200 && code < 300.
Подсказка 2
Последняя ветка else возвращает «неизвестно» — она ловит и 99, и всё, что выше 599.
ЗАДАЧА 1.3
Найди баг: сломанный JSON
средняя
Сервер прислал текст, но JSON.parse на нём падает. Исправьте текст (не трогая остальной код) так, чтобы он стал корректным JSON.
В JSON ключи тоже пишутся в кавычках, и кавычки только двойные. Внутри обычной строки JS их придётся экранировать: \" — или заключить всю строку в одинарные кавычки.
Подсказка 2
Ещё одна ошибка спряталась в конце: запятая после последнего значения в JSON запрещена. Правильно: '{"name": "Мадина", "age": 17, "active": true}'.
ЗАДАЧА 1.4
Собери адрес с параметрами
сложная
Напишите функцию buildUrl(base, params), которая приклеивает к адресу параметры из объекта. Пользуйтесь URLSearchParams — он сам расставит & и закодирует русские буквы.
new URLSearchParams(params).toString() превращает объект в строку вида q=...&page=2.
Подсказка 2
Если параметров нет, строка получится пустая — тогда знак ? не нужен: return query ? base + "?" + query : base;
02 fetch: первый запрос
fetch(url) — встроенная функция браузера для HTTP-запросов. Подключать ничего не нужно. Она возвращает промис (урок 13), поэтому с ней работают через then или, удобнее, через async / await. Попробуем взять одну задачу из учебного сервера jsonplaceholder — он создан специально, чтобы на нём тренироваться:
Строк мало, но здесь есть дваawait, и это не случайность. Давайте разберёмся, зачем.
fetch(url) отправляет запрос. Промис выполнится, как только придёт начало ответа: статус и заголовки.
await даёт нам объект response — это «конверт»: в нём статус и заголовки, а тело ещё может ехать по сети.
response.json() дочитывает тело и превращает JSON-текст в объект. Это тоже промис — поэтому второй await.
Аналогия из жизни
Вы заказали доставку из пекарни. Сначала курьер звонит: «Заказ принят, еду» — это response со статусом. Потом он поднимается к вам с коробкой — это response.json(). Звонок и коробка приходят в разное время, поэтому ждать приходится дважды.
Что есть у response
response.status
Число — код статуса: 200, 404, 500…
response.ok
true, если статус от 200 до 299. Самая полезная проверка — к ней мы вернёмся в разделе 04.
response.headers
Заголовки ответа. Читаются методом get: response.headers.get("content-type").
response.json()
Прочитать тело как JSON → получим объект или массив.
response.text()
Прочитать тело как обычный текст (удобно, если пришёл не JSON).
Забыть await перед fetch или перед response.json(). Тогда в переменной окажется не данные, а промис, и все обращения к полям дадут undefined.
Ошибка: нет второго await · Ctrl + Enter
asyncfunctionmain() {
const response = awaitfetch("https://jsonplaceholder.typicode.com/todos/1");
const todo = response.json(); // ✖ забыли await — это промис, а не объект
console.log(todo.title); // undefined
}
main();
Два шага: const response = await fetch(…), затем const post = await response.json().
Подсказка 2
После этого: console.log(post.userId); console.log(post.id);
ЗАДАЧА 2.2
Осмотр конверта
лёгкая
Запросите https://jsonplaceholder.typicode.com/users/1. Не читая тело, выведите: статус, ok и ответ на вопрос «содержит ли заголовок content-type слово json» (метод строки includes).
Можно редактировать · Ctrl + Enter
asyncfunctionmain() {
// fetch, потом три строки вывода
}
main();
Ожидаемый результат
200
true
true
Подсказка 1
Свойства response.status и response.ok читаются без await — они уже пришли.
В каком порядке метки попадут в массив? Вспомните урок 13: пока fetch ждёт ответ, JavaScript не стоит на месте. Впишите прогноз в prediction и запустите.
Функция main работает синхронно до первого await: метка A ставится сразу. Дальше она «засыпает», и управление возвращается наружу — к метке D.
Подсказка 2
После D код ждёт ответа: сначала продолжится после первого await (B), потом после второго (C). Итого: A D B C.
ЗАДАЧА 2.4
Две зависимых загрузки
средняя
У задачи /todos/1 есть поле userId — номер автора. Загрузите задачу, затем возьмите автора по адресу /users/ + userId и выведите: Задача «название» у имя. Адрес сервера: https://jsonplaceholder.typicode.com.
Адрес второго запроса собираем из данных первого: `${API}/users/${todo.userId}`.
Подсказка 2
Каждый запрос — это пара: const r = await fetch(…); const data = await r.json();. Таких пар у вас будет две.
03 Тело ответа: json(), text() и параметры запроса
Серверы отдают данные двух видов: один объект (/users/1) и список (/users — массив). После response.json() вы получаете обычные объекты и массивы JavaScript, а с ними вы уже умеете работать: map, filter, деструктуризация, ?..
Скачивать всё подряд — расточительно. Большинство API понимают параметры после знака ?: фильтры, лимиты, поиск. В jsonplaceholder, например, ?_limit=3 вернёт только три записи, а ?userId=2 — только записи второго пользователя.
Строка await (await fetch(url)).json() — сжатая запись двух шагов. Читается тяжелее, поэтому пока лучше писать по шагам.
json() или text()?
json() ожидает на входе корректный JSON. Если сервер прислал HTML-страницу с ошибкой или пустой ответ, json() бросит SyntaxError. А text() всегда вернёт текст — его можно показать или записать в лог. Чтобы потренироваться без интернета, мы можем создать «поддельный» ответ: конструктор new Response(текст) делает точно такой же объект, как от настоящего сервера.
Тело ответа можно прочитать только один раз. Вызвали text() — потом json() уже не сработает: данные «выпиты». Читайте тело один раз и сохраняйте результат в переменную.
Этот код выдаст ошибку · Ctrl + Enter
asyncfunctionmain() {
const response = newResponse(JSON.stringify({ city: "Душанбе" }));
const text = await response.text(); // тело прочитаноconst data = await response.json(); // ✖ читаем второй раз
console.log(text, data);
}
main();
Разработчик хотел сначала сохранить «сырой» текст ответа для лога, а потом получить из него данные. Код падает. Исправьте его так, чтобы тело читалось один раз: сначала text(), а объект получите через JSON.parse.
Проблема в строке await response.json() — тело уже прочитано методом text().
Подсказка 2
Замените её на const data = JSON.parse(raw); — текст у нас уже есть в переменной raw.
04 Ошибки: 404 — это не ошибка fetch
Это самое неожиданное место для новичков. У запроса может быть два совершенно разных вида неудач — и fetch относится к ним по-разному.
Аналогия из жизни
Вы едете на маршрутке в Рудаки. Сетевая ошибка — маршрутка сломалась по дороге и вы вообще не доехали (нет интернета, неверный адрес сервера, сервер выключен). Ответ с кодом 404 или 500 — вы доехали, зашли в магазин, а продавец говорит «такого товара нет» или «у нас сегодня закрыто». Вы связались с сервером, он ответил — просто не тем, на что вы рассчитывали.
Ситуация
Что делает fetch
Как узнать
Нет сети, сервер недоступен, неверный адрес
Промис отклоняется (ошибка TypeError)
Сработает catch
Сервер ответил 404, 500 и т. п.
Промис выполняется успешно!
Проверить response.ok или response.status
Посмотрите: запрос несуществующей задачи не бросил ошибку. catch молчит, а ok равно false.
404 не попадает в catch · Ctrl + Enter
asyncfunctionmain() {
try {
const response = awaitfetch("https://jsonplaceholder.typicode.com/todos/99999");
console.log("запрос дошёл до сервера");
console.log("статус:", response.status, "ok:", response.ok);
} catch (error) {
console.log("сюда мы не попадём");
}
}
main();
Правильная схема: проверяем ok и сами бросаем ошибку
Чтобы обе неудачи обрабатывались в одном месте, превращаем «плохой статус» в настоящую ошибку:
Шаблон, который вы будете писать всегда · Ctrl + Enter
Когда запрос не смог добраться до сервера, fetch отклоняет промис, и catch получает ошибку типа TypeError. Чтобы сымитировать это, возьмём адрес, которого точно не существует (зона .invalid зарезервирована под «несуществующие» адреса):
Текст самой ошибки в разных браузерах разный (в Chrome — Failed to fetch, в Safari — Load failed), поэтому полагаться на error.message нельзя. Надёжнее смотреть на error.name или просто показать пользователю свой понятный текст.
Ещё одна причина «Failed to fetch» — CORS
Браузер не даёт страницам читать ответы чужих серверов, если те явно не разрешили (заголовок Access-Control-Allow-Origin). Публичные учебные API разрешают всем, поэтому у нас всё работает. Но когда вы запросите «закрытый» сервер, в консоли появится красное сообщение про CORS, а fetch выдаст ту же сетевую ошибку. Это не баг вашего кода: нужно, чтобы владелец сервера разрешил запросы с вашего сайта. Подробнее разберём в следующих уроках.
Когда тело — не JSON
Третья неприятность: сервер ответил, но в теле не JSON (например, HTML-страница «Сервис временно недоступен»). Тогда response.json() бросит SyntaxError. Проверим на поддельном ответе:
Тело не JSON · Ctrl + Enter
asyncfunctionmain() {
const response = newResponse("<html>Сервис временно недоступен</html>", { status: 503 });
try {
const data = await response.json();
console.log(data);
} catch (error) {
console.log("Не удалось разобрать JSON. Тип ошибки:", error.name);
}
}
main();
Частая ошибка
Сразу делать response.json() и надеяться на лучшее. Для ответа 404 с пустым телом ({}) код «сработает» и выдаст undefined вместо данных — а настоящая причина (запись не найдена) останется незамеченной.
Ошибка: нет проверки ok · Ctrl + Enter
asyncfunctionmain() {
const response = awaitfetch("https://jsonplaceholder.typicode.com/todos/99999");
const todo = await response.json(); // ✖ статус 404, но ошибки нет
console.log("Название задачи:", todo.title); // undefined
}
main();
finally: убираем «Загрузка…» при любом исходе
Блок finally выполняется и после успеха, и после ошибки. Это идеальное место, чтобы спрятать индикатор загрузки или снова включить кнопку (мы делали это в уроке 13).
Запросите https://jsonplaceholder.typicode.com/todos/99999 и выведите одной строкой статус ok — то есть код ответа и значение response.ok через пробел. Тело читать не нужно.
Можно редактировать · Ctrl + Enter
asyncfunctionmain() {
// fetch и одна строка вывода
}
main();
Ожидаемый результат
404 false
Подсказка 1
Достаточно const response = await fetch(…). Тело читать не надо — статус уже в response.
Подсказка 2
console.log(response.status, response.ok); — console.log сам поставит пробел между аргументами.
ЗАДАЧА 4.2
Загрузка с проверкой
средняя
Допишите loadTodo(id): если ответ не ok — бросьте new Error("HTTP " + response.status), иначе верните данные. Затем цикл должен вывести название для задачи 1 и ошибку для 99999.
Можно редактировать · Ctrl + Enter
asyncfunctionloadTodo(id) {
const response = awaitfetch("https://jsonplaceholder.typicode.com/todos/" + id);
// проверка ok и возврат данных
}
asyncfunctionmain() {
for (const id of [1, 99999]) {
try {
const todo = awaitloadTodo(id);
console.log(todo.title);
} catch (error) {
console.log("Ошибка: " + error.message);
}
}
}
main();
Ожидаемый результат
delectus aut autem
Ошибка: HTTP 404
Подсказка 1
Условие: if (!response.ok) { throw new Error(…); } — перед возвратом данных.
Подсказка 2
Возврат: return response.json(); — async-функция сама обернёт результат в промис.
ЗАДАЧА 4.3
Сервер не отвечает
лёгкая
Отправьте запрос на https://no-such-server.invalid/data. Поймайте ошибку и выведите Сеть недоступна: ТипОшибки, подставив error.name.
Можно редактировать · Ctrl + Enter
asyncfunctionmain() {
// try / catch вокруг fetch
}
main();
Ожидаемый результат
Сеть недоступна: TypeError
Подсказка 1
Оберните await fetch(…) в try, а в catch (error) выведите сообщение.
Подсказка 2
console.log("Сеть недоступна: " + error.name);
ЗАДАЧА 4.4
Безопасное чтение JSON
сложная
Напишите readData(response): если статус не ok, она возвращает строку "Ошибка сервера: " + status. Если статус хороший, но тело — не JSON, возвращает "Ответ не JSON". Иначе — сами данные. Ответы поддельные (без интернета), так что результат мгновенный.
Можно редактировать · Ctrl + Enter
asyncfunctionreadData(response) {
// ваш код
}
asyncfunctionmain() {
const answers = [
newResponse(JSON.stringify({ city: "Худжанд" })),
newResponse("{}", { status: 404 }),
newResponse("<html>Привет</html>"),
newResponse("Сервис недоступен", { status: 503 }),
];
for (const response of answers) {
const result = awaitreadData(response);
console.log(typeof result ==="string" ? result : result.city);
}
}
main();
Ожидаемый результат
Худжанд
Ошибка сервера: 404
Ответ не JSON
Ошибка сервера: 503
Подсказка 1
Сначала if (!response.ok) return …. Ранний возврат избавляет от лишней вложенности.
Подсказка 2
Разбор JSON оберните в try { return await response.json(); } catch { return "Ответ не JSON"; } — без await перед response.json() ошибка не попадёт в catch.
ЗАДАЧА 4.5
Три загрузки с finally
сложная
Загрузите по очереди задачи с номерами 1, 99999 и 2. Для каждой: выведите Загружаем #id; при успехе — название, при ошибке — Ошибка: HTTP 404 (код из ответа); в любом случае — строку — готово (через finally).
Можно редактировать · Ctrl + Enter
asyncfunctionloadTodo(id) {
const response = awaitfetch("https://jsonplaceholder.typicode.com/todos/" + id);
if (!response.ok) {
thrownewError("HTTP " + response.status);
}
return response.json();
}
asyncfunctionmain() {
for (const id of [1, 99999, 2]) {
// try / catch / finally
}
}
main();
Ожидаемый результат
Загружаем #1
delectus aut autem
— готово
Загружаем #99999
Ошибка: HTTP 404
— готово
Загружаем #2
quis ut nam facilis et officia qui
— готово
Подсказка 1
Строку «Загружаем» выводите перед try. Внутри цикла нужны все три блока: try, catch, finally.
Три ключа настроек: что делаем, как описываем тело, и само тело (всегда строка)
О нашем учебном сервере
jsonplaceholder — «притворный» сервер: он принимает POST, PUT и DELETE, отвечает как настоящий, но ничего не сохраняет. Создали пост — а при следующем запросе его нет. Для обучения это идеально: нельзя ничего сломать. Настоящие серверы, конечно, данные запоминают.
POST: создаём пост · Ctrl + Enter
asyncfunctionmain() {
const response = awaitfetch("https://jsonplaceholder.typicode.com/posts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title: "Мой первый пост", body: "Салом, мир!", userId: 1 }),
});
const created = await response.json();
console.log("статус:", response.status); // 201 — Created
console.log("id:", created.id); // сервер присвоил номер
console.log("заголовок:", created.title); // и вернул то, что мы отправили
}
main();
method: "POST" — сообщаем серверу, что хотим создать запись.
Content-Type: application/json — предупреждаем: «в теле лежит JSON». Без этого сервер может не понять тело.
JSON.stringify(...) — тело запроса должно быть строкой, объект напрямую отправить нельзя.
В ответ сервер вернёт 201 Created и созданную запись — с новым id.
PUT, PATCH и DELETE
Те же настройки, другой метод. Адрес теперь указывает на конкретную запись: /posts/1.
PUT, PATCH и DELETE · Ctrl + Enter
const API = "https://jsonplaceholder.typicode.com";
const json = { "Content-Type": "application/json" };
asyncfunctionmain() {
// PUT — заменить запись целикомlet response = awaitfetch(API + "/posts/1", {
method: "PUT",
headers: json,
body: JSON.stringify({ title: "Новый заголовок", body: "новый текст", userId: 1 }),
});
console.log("PUT:", response.status, (await response.json()).title);
// PATCH — изменить только одно поле
response = awaitfetch(API + "/posts/1", {
method: "PATCH",
headers: json,
body: JSON.stringify({ title: "Только заголовок" }),
});
console.log("PATCH:", response.status, (await response.json()).title);
// DELETE — удалить, тело не нужно
response = awaitfetch(API + "/posts/1", { method: "DELETE" });
console.log("DELETE:", response.status);
}
main();
Частая ошибка
Отправить объект без JSON.stringify и без Content-Type. Браузер превратит объект в строку "[object Object]", и данные пропадут по дороге. Сервер при этом может ответить «успешно» (201) — просто с пустой записью. Всегда проверяйте, что вернул сервер, а не только статус.
Отправьте POST на https://jsonplaceholder.typicode.com/posts с данными { title: "Салом", body: "Первый пост", userId: 1 }. Выведите код статуса и номер, который присвоил сервер.
Можно редактировать · Ctrl + Enter
asyncfunctionmain() {
// fetch с method, headers и body
}
main();
В body положите JSON.stringify({ title: "Салом", body: "Первый пост", userId: 1 }). Номер — в поле id ответа.
ЗАДАЧА 5.2
Фабрика настроек
средняя
Чтобы не писать одно и то же в каждом запросе, напишите функцию makeOptions(method, data). Она возвращает объект настроек с методом, заголовком Content-Type и телом-JSON. Интернет не нужен — мы просто смотрим, что получилось.
Отправьте PUT-запрос на https://jsonplaceholder.typicode.com/posts/1 с данными { title: "Новый заголовок", body: "текст", userId: 1 }. Выведите статус, затем title и id из ответа.
Можно редактировать · Ctrl + Enter
asyncfunctionmain() {
// PUT-запрос
}
main();
Ожидаемый результат
200
Новый заголовок
1
Подсказка 1
Отличие от POST только в method: "PUT" и в адресе: теперь он указывает на запись /posts/1.
Подсказка 2
Сервер возвращает то, что вы отправили, плюс id: console.log(response.status); console.log(data.title); console.log(data.id);
ЗАДАЧА 5.4
Удаляем пост
лёгкая
Отправьте DELETE на https://jsonplaceholder.typicode.com/posts/1 и выведите статус ответа и то, пуст ли ответ (Object.keys(data).length === 0).
Можно редактировать · Ctrl + Enter
asyncfunctionmain() {
// DELETE-запрос
}
main();
Ожидаемый результат
200
true
Подсказка 1
Для DELETE достаточно { method: "DELETE" } — тело и заголовки не нужны.
Подсказка 2
Прочитайте ответ: const data = await response.json(); — сервер вернёт пустой объект {}.
ЗАДАЧА 5.5
Найди баг: данные потерялись
средняя
Запрос возвращает 201, но в ответе нет заголовка поста: created.title равен undefined. Найдите, чего не хватает в настройках, и исправьте.
Тело запроса должно быть строкой JSON, а объект превращается в "[object Object]".
Подсказка 2
Добавьте headers: { "Content-Type": "application/json" } и оберните объект в JSON.stringify(…).
06 Собираем свою обёртку api.js
Посмотрите, сколько повторяющегося кода мы уже написали: адрес сервера, проверка ok, заголовки, JSON.stringify, response.json(). В большом проекте запросов десятки. Решение — одна функция-обёртка, которая делает всё это за вас. Это и есть ваш артефакт урока: файл api.js. Строим его по шагам, и на каждом шаге код работает.
Шаг 1. Одна функция вместо копипасты
Выносим адрес сервера в константу и учимся собирать полный адрес из пути.
Добавляем главное: плохой статус становится ошибкой. Заодно кладём код статуса прямо в ошибку — вызывающему коду он пригодится (например, показать «Не найдено» вместо «Ошибка»).
Запись { method = "GET", body } = {} — это деструктуризация параметра со значениями по умолчанию (урок 7). Благодаря = {} функцию можно вызывать и вовсе без второго аргумента.
Шаг 4. Таймаут: не ждём вечно
Если сервер завис, пользователь будет смотреть на «Загрузка…» до бесконечности. Поэтому добавим ограничение по времени. Для этого у fetch есть «кнопка отмены» — AbortController: вызываем controller.abort(), и запрос прерывается с ошибкой AbortError.
Остаётся спрятать вызов request за короткими именами. Объект api с методами get, post, put, del (слово delete в JS занято) — и код в проекте читается как предложение. Параметры запроса принимаем объектом и собираем через URLSearchParams.
Теперь любая ошибка — сеть, 404, 500, таймаут — прилетает в одинcatch в виде обычной ошибки с понятным сообщением. В следующих уроках мы будем только вызывать api.get(…) и рисовать результат. Сохраните готовый файл как api.js: он будет вашим артефактом урока и пригодится в проекте.
В обёртке нет проверки статуса: запрос несуществующей задачи «успешно» возвращает пустой объект. Добавьте проверку: бросайте ошибку "HTTP " + status и сохраняйте код в error.status. Вызывающий код уже написан.
Создайте ошибку, допишите ей свойство и бросьте: const error = new Error(…); error.status = response.status; throw error;
Подсказка 2
Всё это внутри if (!response.ok) { … } — перед return response.json().
ЗАДАЧА 6.2
Короткие get и post
средняя
Функция request(path, options) уже есть. Допишите объект api с методами get(path) и post(path, body), которые вызывают request. Тело в request уже превращается в JSON само.
get вызывает request(path), а post передаёт метод и тело: request(path, { method: "POST", body }).
Подсказка 2
const api = { get: (path) => request(path), post: (path, body) => request(path, { method: "POST", body }) };
ЗАДАЧА 6.3
Параметры запроса в get
средняя
Научите get принимать второй аргумент — объект параметров, который превращается в строку запроса. Если параметров нет, адрес остаётся прежним. Затем получите 5 задач второго пользователя и проверьте, что все они его.
Можно редактировать · Ctrl + Enter
const BASE_URL = "https://jsonplaceholder.typicode.com";
asyncfunctionrequest(path) {
const response = awaitfetch(BASE_URL + path);
if (!response.ok) {
thrownewError("HTTP " + response.status);
}
return response.json();
}
functionget(path, params) {
// соберите адрес с параметрами и вызовите request
}
asyncfunctionmain() {
const todos = awaitget("/todos", { userId: 2, _limit: 5 });
console.log("задач:", todos.length);
console.log("все от пользователя 2:", todos.every((t) => t.userId ===2));
const one = awaitget("/todos/1");
console.log(one.id);
}
main();
Ожидаемый результат
задач: 5
все от пользователя 2: true
1
Подсказка 1
Строка запроса: new URLSearchParams(params).toString(). Она нужна, только если params передали.
Добавьте в request таймаут через AbortController: по истечении timeout мс запрос отменяется, а наружу летит ошибка с текстом "Таймаут". Таймер надо очистить в finally. Вызов с timeout: 1 обязан упасть, а обычный вызов — сработать.
Нужны: const controller = new AbortController();, setTimeout(() => controller.abort(), timeout) и fetch(url, { signal: controller.signal }).
Подсказка 2
Оберните запрос в try / catch / finally. В catch: если error.name === "AbortError" — throw new Error("Таймаут"), иначе пробросьте ошибку дальше. В finally: clearTimeout(timer).
07 ИИ-блок: обёртка над fetch от ИИ и её проверка
Обёртку над fetch ИИ пишет быстро и красиво. Но именно здесь он любит «срезать углы»: забывает проверить response.ok, не ловит ответ без JSON или не различает, что именно сломалось. Поэтому сегодняшний навык — не «получить код», а проверить его на плохих ответах: 404, 500, не-JSON и пропавшая сеть.
Правила курса, которые здесь особенно важны
1. Сначала 15 минут пробуете сами: вспомните шаблон из раздела 04 и шаги обёртки из раздела 06. 2. Только потом идёте к ИИ. 3. Не вставляйте в проект код, который не можете объяснить строчка за строчкой — для обёртки это значит: вы знаете, зачем каждая проверка. 4. Проверяйте не на «счастливом пути» (200 OK), а на ошибочных ответах: ИИ почти всегда справляется с хорошим сценарием и ошибается на плохих.
Как просить: плохо и хорошо
Формула промпта курса: контекст → задача → ограничения → формат ответа → проверка.
напиши функцию для fetch
Я учу JavaScript, уровень — новичок, знаю промисы, async/await и try/catch. Напиши обёртку над fetch: базовый URL, таймаут через AbortController, проверка response.ok, понятные ошибки. Чистый JS, без библиотек, одним файлом, не больше 40 строк, с комментариями на русском к каждому блоку. Формат ответа: код, затем список «что делает каждая проверка» в 5 пунктах. В конце скажи, какие ошибочные сценарии мне нужно проверить вручную.
После ответа ИИ прогоните код по чек-листу. Если хотя бы один пункт «нет» — вы нашли баг:
Чек-лист проверки обёртки
1) На ответе 404 вылетает ошибка с кодом статуса? 2) На ответе 500 — то же? 3) Если пришёл не JSON, ошибка понятная, а не «Unexpected token <»? 4) При выключенной сети (отключите Wi-Fi) пользователь видит ваш текст, а не страшный стек? 5) Есть таймаут, и таймер очищается в finally? 6) Тело не читается дважды? 7) Вы можете объяснить каждую строку?
Проверять на настоящем сервере неудобно: нельзя заставить jsonplaceholder «упасть» по заказу. Поэтому профессионалы подменяют сервер на тестовый стенд — функцию, которая отдаёт заранее заготовленные ответы. В задаче ниже именно такой стенд, и вам предстоит проверить и починить «обёртку от ИИ».
Практика: ловим ошибки ИИ
ЗАДАЧА 7.1
Проверь и почини обёртку от ИИ
сложная
ИИ выдал функцию aiRequest. Запустите стенд — увидите, что на плохих ответах она ведёт себя неправильно. Перепишите толькоaiRequest так, чтобы сценарии вели себя по таблице: ok → данные, notfound → ошибка HTTP 404, server → HTTP 500, html (статус 200, но тело не JSON) → ответ не JSON, network (запрос не дошёл) → сеть недоступна.
Можно редактировать · Ctrl + Enter
// --- тестовый стенд, не меняйте ---functionfakeFetch(scenario) {
switch (scenario) {
case"ok": return Promise.resolve(newResponse(JSON.stringify({ name: "Мадина" })));
case"notfound": return Promise.resolve(newResponse("{}", { status: 404 }));
case"server": return Promise.resolve(newResponse("Internal Server Error", { status: 500 }));
case"html": return Promise.resolve(newResponse("<html>Привет</html>"));
case"network": return Promise.reject(newTypeError("Failed to fetch"));
}
}
// --- код от ИИ: починить ---asyncfunctionaiRequest(scenario) {
const response = awaitfakeFetch(scenario);
const data = await response.json();
return data;
}
// --- проверка, не меняйте ---asyncfunctionmain() {
for (const scenario of ["ok", "notfound", "server", "html", "network"]) {
try {
const data = awaitaiRequest(scenario);
console.log(scenario + ": данные получены: " + data.name);
} catch (error) {
console.log(scenario + ": Ошибка: " + error.message);
}
}
}
main();
Ожидаемый результат
ok: данные получены: Мадина
notfound: Ошибка: HTTP 404
server: Ошибка: HTTP 500
html: Ошибка: ответ не JSON
network: Ошибка: сеть недоступна
Подсказка 1
Три места требуют защиты: сам fakeFetch (сеть), проверка response.ok (404 и 500) и response.json() (не JSON). Каждое — в своём try / catch или if.
Подсказка 2
Заверните: let response; try { response = await fakeFetch(scenario); } catch { throw new Error("сеть недоступна"); }. Затем if (!response.ok) throw new Error("HTTP " + response.status);. Затем try { return await response.json(); } catch { throw new Error("ответ не JSON"); }.
ЗАДАЧА 7.2
Свой промпт и проверка
лёгкая
Составьте промпт для ИИ по формуле курса (контекст → задача → ограничения → формат → проверка): пусть ИИ напишет обёртку api.js для вашего сценария — например, для запросов к dummyjson.com. Отправьте его, получите код и прогоните по чек-листу из этого раздела. Запишите в комментарии над кодом, какие из 7 пунктов провалились и как вы это исправили. Автоматической проверки нет — отметьте задачу вручную, когда будет готово.
Можно редактировать · Ctrl + Enter
// Мой промпт:// ...// Что не прошло по чек-листу и как исправил:// ...
Подсказка 1
Если ИИ не вернул нужного, не переписывайте всё заново: напишите ему конкретный провал — «на ответе 404 функция вернула пустой объект, а не бросила ошибку».
Подсказка 2
Сравните код ИИ со своим из раздела 06. Чем отличается? Если отличие вам непонятно — это и есть повод спросить, а не копировать.
08 Практика на уроке: первый запрос к API и вывод на страницу
Теперь соберём всё вместе: возьмём данные из публичного API, выведем в консоль, а затем нарисуем на странице. Идём шагами — после каждого шага код работает.
Шаг 1. Данные в консоли
Всегда начинайте с консоли: сначала убедитесь, что данные приходят и вы понимаете их форму, и только потом рисуйте интерфейс.
Данные из fetch — это те же массивы объектов, что и в уроках 6–8. Значит, превращаем их в элементы тем же способом: createElement и append. Нажмите «▶ Запустить» и посмотрите на список справа.
Обратите внимание на структуру: «Загрузка…» → данные или сообщение об ошибке. Пользователь не должен смотреть на пустую страницу и гадать, что происходит. Подробно про такие состояния поговорим в уроке 17.
Практика: свой запрос на странице
ЗАДАЧА 8.1
Список задач на странице
средняя
Загрузите 5 первых задач (/todos?_limit=5) и выведите их в список: у выполненной задачи перед названием стоит «✔ », у невыполненной — «○ ». Когда всё нарисовано, выведите в консоль Показано: 5 (число элементов li в списке).
Мои задачи · Ctrl + Enter
<h3>Мои задачи</h3><ulid="todos"></ul>
body {font-family: system-ui, sans-serif; padding: 12px; }
li {list-style: none; margin: 4px0; }
const list = document.querySelector("#todos");
asyncfunctionmain() {
// 1) fetch https://jsonplaceholder.typicode.com/todos?_limit=5// 2) для каждой задачи создайте li: "✔ название" или "○ название"// 3) выведите console.log("Показано: " + list.children.length)
}
main();
Предпросмотр
Нажмите «Запустить», чтобы увидеть результат.
Ожидаемый результат
Показано: 5
Подсказка 1
Запрос и разбор такие же, как в шаге 2. Внутри цикла выберите значок: const mark = todo.completed ? "✔ " : "○ ";
Подсказка 2
li.textContent = mark + todo.title; list.append(li); — а console.log ставьте после цикла, когда элементы уже добавлены.
ЗАДАЧА 8.2
Два API на одной странице
сложная
Загрузите одновременно (Promise.all) данные из двух разных серверов: пользователей https://jsonplaceholder.typicode.com/users?_limit=3 и товары https://dummyjson.com/products?limit=3&select=title,price (в ответе товары лежат в поле products). Выведите два списка: имена пользователей и «название — цена $». В консоль выведите Пользователей: 3, товаров: 3. Если любой из запросов упал — покажите ошибку в блоке #status.
const [users, productsData] = await Promise.all([load(URL1), load(URL2)]); — оба запроса стартуют одновременно. Список товаров: productsData.products.
Подсказка 2
Для каждого списка свой цикл forEach с createElement("li"). Цена в тексте: product.title + " — " + product.price + " $".
09 Домашнее задание
Получить данные из двух разных API и отрисовать их на странице. Артефакт — файл api.js: ваша собственная обёртка над fetch, через которую идут оба API.
Что нужно сделать
Выберите два публичных API без ключа и с CORS (проверьте в браузере, что ответ приходит): jsonplaceholder.typicode.com, dummyjson.com, api.open-meteo.com (погода — задайте координаты Душанбе: latitude=38.56&longitude=68.77¤t=temperature_2m), rickandmortyapi.com. Важно: сервера должны быть разные.
Напишите api.js. Функция request с проверкой response.ok, ошибкой со status, таймаутом, и сокращения get / post. Базовый адрес сделайте параметром (один request на два сервера).
Отрисуйте данные первого API — список или карточки (минимум 5 элементов, минимум 2 поля у каждого).
Отрисуйте данные второго API — другой формат (например, таблица, один большой блок или карточки с картинкой).
Три состояния для каждого блока: «Загрузка…», данные, понятная ошибка. Проверьте ошибку: измените адрес на неправильный и убедитесь, что пользователь видит сообщение, а не пустоту.
Одно POST-действие. Отправьте любую форму или кнопкой данные на jsonplaceholder.typicode.com/posts и покажите на странице ответ сервера (присвоенный id).
Проверка ИИ. Если использовали ИИ — приложите свой промпт и список ошибочных сценариев, которые вы проверили (404, сеть, не JSON).
Что сдавать
Ссылку на репозиторий в GitHub: index.html, api.js, app.js, style.css.
Два скриншота: страница с данными и страница с состоянием ошибки.
Короткий README: какие два API вы выбрали и чем они отличаются.
Как оцениваем (0–10)
Что
Баллы
api.js: проверка ok, понятные ошибки, get / post
3
Данные двух разных API выведены на страницу
2
Состояния «Загрузка…» и «Ошибка» работают у обоих блоков
2
POST-запрос с показом ответа сервера
1
Таймаут и очистка таймера в finally
1
Чистый код: нет повторов запроса, имена понятные, ИИ-код вы можете объяснить
1
Перед сдачей
Отключите интернет (или поставьте в DevTools на вкладке Network режим Offline) и перезагрузите страницу. Если вместо понятной ошибки вы видите пустой экран или красное в консоли — доработайте обработку ошибок.
✓ Шпаргалка урока
HTTP: клиент шлёт запрос (метод + URL + заголовки + тело), сервер отвечает (статус + заголовки + тело). Данные чаще всего в формате JSON.
Методы:GET — получить, POST — создать, PUT — заменить, PATCH — изменить часть, DELETE — удалить.