Перейти к содержимому

81. Обработка ошибок

Иллюстрация к уроку

В мире разработки редко что-то идет идеально. Ошибки — это неотъемлемая часть любого приложения. Эффективная обработка ошибок критически важна для создания надежного, стабильного и приятного в использовании продукта. Она позволяет вашему приложению “грациозно” реагировать на непредвиденные ситуации, предотвращая крахи и предоставляя пользователю полезную обратную связь.

В JavaScript мы используем конструкции try...catch...finally и механизмы работы с промисами для управления ошибками.

Блок try...catch — это фундамент обработки синхронных ошибок.

// Пример 1: Базовый try...catch
function divide(a, b) {
try {
if (b === 0) {
throw new Error("Деление на ноль недопустимо!"); // Генерируем ошибку
}
return a / b;
} catch (error) {
console.error("Произошла ошибка при делении:", error.message);
return null; // Возвращаем безопасное значение или перебрасываем ошибку
}
}
console.log(divide(10, 2)); // 5
console.log(divide(10, 0)); // Произошла ошибка при делении: Деление на ноль недопустимо! (и null)

Для более детальной обработки можно создавать собственные типы ошибок, наследуясь от встроенного Error.

// Пример 2: Кастомная ошибка
class ValidationError extends Error {
constructor(message, field) {
super(message); // Вызываем конструктор базового Error
this.name = "ValidationError"; // Устанавливаем имя ошибки
this.field = field; // Добавляем специфичные данные
}
}
function validateInput(username) {
if (username.length < 3) {
throw new ValidationError("Имя пользователя слишком короткое", "username");
}
return true;
}
try {
validateInput("ab");
} catch (error) {
if (error instanceof ValidationError) {
console.error(`Ошибка валидации поля ${error.field}: ${error.message}`);
} else {
console.error("Неизвестная ошибка:", error);
}
}
// Ошибка валидации поля username: Имя пользователя слишком короткое

Блок finally всегда выполняется, независимо от того, была ли ошибка выброшена или нет. Он идеально подходит для очистки ресурсов.

// Пример 3: Использование finally
function processFile(filename) {
let fileHandle;
try {
// Имитация открытия файла
if (!filename) throw new Error("Имя файла не указано!");
fileHandle = `open(${filename})`;
console.log(`${fileHandle} открыт.`);
// Имитация обработки, которая может вызвать ошибку
if (filename === "broken.txt") {
throw new Error("Ошибка чтения файла!");
}
console.log("Файл успешно обработан.");
return true;
} catch (error) {
console.error("Ошибка при обработке файла:", error.message);
return false;
} finally {
if (fileHandle) {
console.log(`${fileHandle} закрыт.`); // Всегда закрываем ресурс
}
}
}
processFile("data.txt");
// open(data.txt) открыт.
// Файл успешно обработан.
// open(data.txt) закрыт.
processFile("broken.txt");
// open(broken.txt) открыт.
// Ошибка при обработке файла: Ошибка чтения файла!
// open(broken.txt) закрыт.

Обработка Ошибок в Асинхронном Коде (Промисы)

Заголовок раздела «Обработка Ошибок в Асинхронном Коде (Промисы)»

Для промисов используем метод .catch() или try...catch с async/await.

// Пример 4: Обработка ошибок в Промисах (.catch())
function fetchData(url) {
return new Promise((resolve, reject) => {
if (!url.startsWith("http")) {
return reject(new Error("Некорректный URL!"));
}
setTimeout(() => {
// Имитация сетевого запроса
if (url === "http://bad-api.com") {
return reject(new Error("Ошибка сервера 500!"));
}
resolve(`Данные с ${url}`);
}, 100);
});
}
fetchData("http://good-api.com")
.then(data => console.log(data))
.catch(error => console.error("Ошибка Promise:", error.message));
// Данные с http://good-api.com
fetchData("ftp://bad-url.com")
.then(data => console.log(data))
.catch(error => console.error("Ошибка Promise:", error.message));
// Ошибка Promise: Некорректный URL!

async/await позволяет использовать привычный try...catch для асинхронного кода, делая его более читаемым.

// Пример 5: try...catch с async/await
async function getAndProcessData(url) {
try {
const data = await fetchData(url); // await может вызвать ошибку
console.log("Полученные данные:", data);
// Дополнительная обработка данных
} catch (error) {
console.error("Ошибка в async функции:", error.message);
// Здесь можно показать сообщение пользователю или отправить лог
}
}
getAndProcessData("http://valid-api.com");
// Полученные данные: Данные с http://valid-api.com
getAndProcessData("http://bad-api.com");
// Ошибка в async функции: Ошибка сервера 500!

Иногда нужно поймать ошибку, что-то с ней сделать (например, залогировать), а затем перебросить ее дальше, чтобы вышестоящий код тоже мог ее обработать.

// Пример 6: Переброс ошибки
function processPayment(amount) {
try {
if (amount <= 0) {
throw new Error("Сумма платежа должна быть положительной.");
}
console.log(`Платеж ${amount} успешно обработан.`);
return true;
} catch (error) {
console.error("Лог: Проблема с платежом:", error.message);
throw error; // Перебрасываем ошибку дальше
}
}
function handleTransaction(orderId, paymentAmount) {
try {
processPayment(paymentAmount);
console.log(`Транзакция ${orderId} завершена.`);
} catch (error) {
console.error(`Ошибка при выполнении транзакции ${orderId}: ${error.message}`);
}
}
handleTransaction("ORDER-001", 100);
// Платеж 100 успешно обработан.
// Транзакция ORDER-001 завершена.
handleTransaction("ORDER-002", -50);
// Лог: Проблема с платежом: Сумма платежа должна быть положительной.
// Ошибка при выполнении транзакции ORDER-002: Сумма платежа должна быть положительной.

Продвинутое Использование и Глобальная Обработка

Заголовок раздела «Продвинутое Использование и Глобальная Обработка»

Для более сложных приложений полезно настроить глобальные обработчики.

  • window.onerror: Перехватывает необработанные ошибки синхронного кода в браузере.
  • window.onunhandledrejection: Перехватывает необработанные ошибки промисов в браузере.
  • process.on('uncaughtException') и process.on('unhandledRejection'): Аналогичные механизмы в Node.js.
// Пример 7: Глобальный обработчик (браузер)
window.onerror = function(message, source, lineno, colno, error) {
console.error("Глобальная ошибка (синхронная):", { message, source, lineno, colno, error });
// Здесь можно отправить лог на сервер
return true; // Возвращаем true, чтобы предотвратить стандартное поведение браузера (например, вывод в консоль)
};
window.onunhandledrejection = function(event) {
console.error("Глобальная ошибка (Promise):", event.reason);
// Здесь также можно отправить лог на сервер
};
// Пример для демонстрации (вызовет глобальные обработчики)
// setTimeout(() => { throw new Error("Тестовая синхронная ошибка!"); }, 100);
// Promise.reject("Тестовый отклонённый Promise!");
  1. “Проглатывание” ошибок: Пустой блок catch или catch без логирования – худшая практика. Вы теряете информацию о проблеме.
    // Плохо:
    try {
    //...
    } catch (error) {
    // Ничего не делаем, ошибка исчезает
    }
  2. Неправильная обработка асинхронных ошибок: try...catch не будет работать напрямую с промисами, если вы не используете await.
    // Не сработает для Promise, если нет await:
    try {
    myPromiseFunction(); // Промис выполнится асинхронно
    } catch (error) {
    // Эта ошибка не будет поймана, т.к. Promise не был завершен к моменту выхода из try
    }
  3. Слишком общая обработка: Попытка поймать все возможные ошибки одним try...catch может скрыть реальные проблемы. Используйте кастомные ошибки или условную логику в catch.
  • Логируйте ошибки: Всегда выводите ошибки в консоль или, лучше, отправляйте их в систему мониторинга (например, Sentry, Bugsnag).
  • Сообщайте пользователю: Предоставляйте понятные, дружелюбные сообщения об ошибках, вместо технических стектрейсов.
  • Используйте try...catch целенаправленно: Не оборачивайте каждый чих в try...catch. Используйте его там, где ожидаете возможные сбои или где нужно восстановиться после ошибки.
  • Тестируйте обработку ошибок: Убедитесь, что ваши обработчики действительно работают, когда происходят ошибки.
  • Используйте finally для очистки: Если у вас есть ресурсы (файлы, сетевые соединения, таймеры), которые нужно закрыть, finally – лучшее место для этого.

Правильная обработка ошибок — это признак профессионального и надежного кода. Освоив эти концепции, вы сможете создавать более устойчивые и удобные приложения.

Попробуйте примеры в интерактивном редакторе: