JavaScript / 37 минут
Fetch, HTTP и отмена запросов
Разделяйте сетевой сбой, HTTP-ошибку, разбор ответа и отмену операции.
- Разделяйте сетевой сбой, HTTP-ошибку, разбор ответа и отмену операции.
- Применить тему в задаче «Fetch, HTTP и отмена запросов» и объяснить граничные случаи.
Около 7 минут на чтение и разбор примеров. Практика — отдельно, в своём темпе.
Три разных уровня успеха
При загрузке данных важно различать успешный обмен по сети, успешный HTTP-статус и подходящую структуру тела. fetch может успешно получить ответ 404: сеть сработала, сервер прислал корректный HTTP-ответ, но нужный ресурс не найден. Затем даже у ответа 200 тело может оказаться невалидным JSON или объектом другой формы. Каждый уровень требует своей проверки.
Не объединяйте все варианты в одну бессодержательную строку «не работает». Сетевой отказ позволяет предложить повторить загрузку, 401 может потребовать входа, 404 — показать отсутствие ресурса, а повреждённый контракт — сигнализировать о несовместимом ответе. При этом публичное сообщение не должно случайно раскрывать приватные детали сервера.
Response и чтение тела
fetch возвращает Promise объекта Response. У него есть status, headers и ok; ok соответствует успешному диапазону HTTP-статусов. Методы json и text также асинхронны, потому что тело поступает как поток. Обычный Response не предназначен для бесконечного повторного чтения одного тела: если нужны два независимых потребителя, модель нужно спроектировать заранее.
async function loadCourses() {
const response = await fetch('/api/courses');
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
if (!Array.isArray(data)) throw new Error('Ожидался массив');
return data;
}В этом примере проверка массива — только начальная валидация. Если каждый курс должен иметь id и title, нужно проверить и элементы. TypeScript-аннотация при json не добавит такую проверку автоматически. Возвращайте уже проверенную модель, чтобы остальному интерфейсу не приходилось гадать о форме данных на каждом шаге.
Отправка JSON и построение URL
Для JSON-запроса обычно указывают метод, Content-Type и сериализованное тело. Не передавайте произвольный объект в body в надежде, что он автоматически станет JSON нужной схемы. Разделяйте форму тела и параметры URL. Для query удобно использовать URLSearchParams вместо ручного соединения символов &, ? и пробелов.
const query = new URLSearchParams({ q: 'JS и TS', page: '2' });
const response = await fetch(`/api/search?${query}`);
await fetch('/api/notes', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ title: 'Новая заметка' }),
});Кодирование URL-компонента не является валидацией его бизнес-смысла и не даёт право на ресурс. Сервер отдельно проверяет структуру, ограничения и пользователя. Аналогично CORS определяет браузерную модель доступа, а не заменяет аутентификацию, авторизацию или защиту cookie-запросов от CSRF.
AbortController и устаревшие результаты
AbortController создаёт signal, который передают запросу. Отмена полезна при переходе на другую страницу, смене поисковой строки или превышении допустимого времени ожидания. Отменённую операцию часто не нужно показывать пользователю как обычную ошибку: он сам уже выбрал другое действие. Но конкретный код должен различать ожидаемую отмену и настоящий отказ.
const controller = new AbortController();
const pending = fetch('/api/search?q=js', { signal: controller.signal });
controller.abort();
try { await pending; } catch (error) {
if (error.name !== 'AbortError') throw error;
}Одной отмены иногда недостаточно для всех гонок. Уже завершившаяся операция или другой источник данных может попытаться применить устаревший результат. Полезно хранить идентификатор актуального запроса и проверять его перед обновлением состояния. Смысл правила прост: более старый поиск не должен перезаписывать результат более нового только из-за другого времени ответа.
Состояния интерфейса и повторные запросы
Модель загрузки обычно различает pending, success, empty и error. Пустой успешный список не равен ошибке; повторная загрузка не всегда требует стирать уже показанные данные. При изменении запроса нужно решить, можно ли временно оставить старый результат, как отметить его неактуальность и где показать действие повтора.
Повтор запроса тоже требует смысла. Безопасный GET часто можно повторить, а создание заказа может требовать идемпотентного ключа, чтобы не выполнить операцию дважды. Не добавляйте автоматические бесконечные повторы всех POST. Ограничивайте количество попыток и различайте временные сетевые сбои, постоянные ошибки ввода и отказ в доступе.
Практика без внешней сети
В задании используется настоящий Response, созданный локально. Это позволяет проверить HTTP-ветвление и чтение JSON без нестабильного внешнего сервера. Для 200 и 201 нужно вернуть разобранное тело, для ошибок — объект с переданным status. Нельзя считать любой полученный Response успешным или всегда возвращать входной data, пропуская требуемое чтение.
Проверьте объект, массив и null в успешной ветви, а также 404 и 500 в ошибочной. Объясните, на какой именно стадии могло бы возникнуть исключение при повреждённом JSON. После решения добавьте к учебному загрузчику проверку схемы и отмену. Сначала опишите состояния и переходы, затем пишите обработчики: это предотвращает случайное зависание кнопки в состоянии загрузки.