Глава 4.4: Обработка ошибок
Время изучения: 45 минут
1. Обработка ошибок: Аварийные протоколы ЦУП
В космосе все может пойти не по плану: солнечная вспышка может прервать связь, в бортовом компьютере корабля может произойти сбой, а команда с Земли может содержать неверные координаты.
Обработка ошибок на фронтенде — это аварийные протоколы вашего ЦУПа. Они должны:
- 🚨 Не дать всему интерфейсу "взорваться" из-за одной неудачной команды.
- 📡 Четко сообщить оператору (пользователю), что именно пошло не так.
- 🔧 Предложить возможные дальнейшие действия.
💡 Космическая аналогия:
Если от корабля приходит сигнал
500 Internal Server Error
, на дисплее ЦУПа не должно появляться "Критическая ошибка JavaScript в строке 57". Вместо этого должно быть: "🚨 Сбой на борту корабля! Инженеры уже уведомлены. Попробуйте повторить команду позже."
2. Типы "космических аномалий"
На фронтенде мы сталкиваемся с тремя основными типами ошибок при работе с API:
- Сетевые ошибки: Связь с сервером не установлена. Антенна не работает, кабель перерезан.
fetch
"упадет" в блок.catch()
. - Клиентские ошибки (4xx): Команда с Земли была некорректной. Неверный ID, ошибка валидации. Сервер отвечает, но со статусом
4xx
. - Серверные ошибки (5xx): Сбой на самом корабле. Проблема в коде API. Сервер отвечает, но со статусом
500+
.
Мы уже начали обрабатывать их с помощью try...catch
и проверки response.ok
. Теперь давайте сделаем это централизованно.
3. Централизованная функция-обработчик
Повторять один и тот же код try...catch
в каждой функции — плохая практика. Создадим универсальную "обертку" для наших fetch
-запросов.
Шаг 1: Создаем api.js
Создайте новый файл api.js
рядом с app.js
. В него мы вынесем всю логику взаимодействия с API.
// api.js
const API_BASE_URL = 'http://127.0.0.1:8000';
/**
* Универсальная функция для выполнения запросов к API.
* Обрабатывает ошибки и возвращает JSON.
* @param {string} endpoint - Конечная точка API, например, '/spaceships'
* @param {object} options - Параметры для fetch (method, headers, body)
*/
async function apiRequest(endpoint, options = {}) {
const url = `${API_BASE_URL}${endpoint}`;
try {
const response = await fetch(url, options);
// Если ответ вообще не JSON, то сразу выбрасываем ошибку
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
// Исключение для успешного DELETE запроса, который не имеет тела
if (response.status === 204) return null;
throw new TypeError(`Получен не-JSON ответ от сервера: ${response.statusText}`);
}
const data = await response.json();
if (!response.ok) {
// Если сервер вернул JSON с ошибкой (например, detail от FastAPI)
const errorMessage = data.detail || `HTTP ошибка! Статус: ${response.status}`;
throw new Error(errorMessage);
}
return data;
} catch (error) {
console.error(`Ошибка API запроса к ${endpoint}:`, error);
// "Пробрасываем" ошибку дальше, чтобы ее можно было поймать в UI
throw error;
}
}
Шаг 2: Подключаем api.js
в index.html
Важно подключить его ДО app.js
, так как app.js
будет использовать его функции.
<!-- index.html -->
<body>
<!-- ... -->
<script src="api.js"></script>
<script src="app.js"></script>
</body>
Шаг 3: Рефакторим app.js
Теперь перепишем наши функции, используя новый apiRequest
.
// app.js
// const API_BASE_URL = ...; // Эту строку можно удалить, она теперь в api.js
// ...
async function fetchAndDisplayFleet() {
try {
fleetList.innerHTML = '<li>Загрузка телеметрии...</li>';
const ships = await apiRequest('/spaceships'); // <-- Используем нашу обертку!
fleetList.innerHTML = '';
if (ships.length === 0) {
fleetList.innerHTML = '<li>В реестре нет ни одного аппарата.</li>';
return;
}
ships.forEach(ship => { /* ... остальной код отображения ... */ });
} catch (error) {
fleetList.innerHTML = `<li>🔴 Ошибка загрузки флота: ${error.message}</li>`;
}
}
async function createShip(event) {
event.preventDefault();
const shipData = { /* ... сбор данных из формы ... */ };
try {
createStatusMessage.textContent = 'Отправка команды на запуск...';
const options = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(shipData)
};
const newShip = await apiRequest('/spaceships', options); // <-- Используем нашу обертку!
createStatusMessage.textContent = `🚀 Успешный запуск! Аппарату присвоен ID: ${newShip.id}`;
createShipForm.reset();
fetchAndDisplayFleet();
} catch (error) {
createStatusMessage.textContent = `🔴 Ошибка: ${error.message}`;
}
}
// Перепишите остальные функции (fetchShipById, deleteShip) по аналогии!
response.ok
и парсинга JSON находится в одном месте, а код в app.js
стал намного чище и читаемее.
4. Отображение ошибок пользователю
Хороший интерфейс должен не просто писать ошибку в консоль, а показывать ее пользователю в понятном виде.
Пример: Улучшение createShip
Наш код уже делает это: createStatusMessage.textContent = ...
. Но можно сделать еще лучше, создав универсальную функцию для отображения уведомлений.
Добавляем в app.js
:
// app.js
function showNotification(message, isError = false) {
const notificationArea = document.getElementById('create-status-message'); // или другой элемент
notificationArea.textContent = message;
notificationArea.style.color = isError ? 'red' : 'green';
}
// Используем в createShip:
async function createShip(event) {
// ...
try {
// ...
const newShip = await apiRequest('/spaceships', options);
showNotification(`🚀 Успешный запуск! ID: ${newShip.id}`);
// ...
} catch (error) {
showNotification(`🔴 Ошибка: ${error.message}`, true);
}
}
Квиз для закрепления
🚀 Итог главы:
Вы укрепили свой ЦУП, создав надежные аварийные протоколы.
- 🛡️ Вы понимаете разницу между сетевыми, клиентскими и серверными ошибками.
- ⚙️ Вы создали централизованную функцию
apiRequest
для обработки всех запросов, избежав дублирования кода. - 📡 Ваш интерфейс теперь умеет корректно сообщать пользователю об ошибках, делая его более дружелюбным и надежным.
Аварийные щиты подняты! Но что лучше: цепочки .then()
или современный async/await
? В следующей главе мы разберем оба подхода и поймем, когда какой использовать.
📌 Проверка:
- Проверьте, что ваш код в
app.js
успешно рефакторен и использует новую функциюapiRequest
.- Попробуйте остановить FastAPI сервер и нажать кнопку "Запросить данные". Вы должны увидеть ошибку соединения на странице.
- Попробуйте создать корабль с невалидными данными. Вы должны увидеть сообщение об ошибке валидации, которое пришло от FastAPI.
⚠️ Если ошибки:
apiRequest is not defined
: Убедитесь, что вы подключилиapi.js
вindex.html
передapp.js
.- Проверьте консоль браузера на наличие других синтаксических ошибок в JavaScript.