Ваше приложение падает с загадочной ошибкой в консоли? Или еще хуже — пользователь видит белый экран вместо интерфейса? Если вы пишете на JavaScript, то наверняка сталкивались с подобным. В этой статье разберем, как превратить хрупкий код в надежную крепость, используя правильную обработку ошибок.
Почему ошибки — это не всегда плохо
Многие разработчики воспринимают ошибки как нечто негативное. Но на самом деле ошибка — это сигнал. Как лампочка «Check Engine» в машине: она не ломает двигатель, а сообщает, что пора заглянуть под капот. Если игнорировать этот сигнал, последствия могут быть печальными.
В JavaScript ошибки делятся на несколько типов: синтаксические (опечатки в коде), логические (неправильные вычисления) и ошибки времени выполнения (например, обращение к несуществующему свойству). Первые ловятся на этапе написания, вторые — при тестировании, а третьи могут выстрелить в самый неподходящий момент у пользователя.
Наша задача — не допустить, чтобы одна ошибка обрушила всё приложение. Для этого и существует обработка ошибок.
Основы: try, catch, finally
Самый простой способ поймать ошибку — обернуть подозрительный код в конструкцию try...catch. Это как страховка: если что-то пойдет не так, мы перехватим ошибку и сделаем что-то разумное вместо падения.
try {
// Код, который может выбросить ошибку
const data = JSON.parse('{ некорректный JSON }');
console.log(data);
} catch (error) {
// Этот блок выполнится, если ошибка произошла
console.error('Ошибка парсинга JSON:', error.message);
// Можно показать пользователю понятное сообщение
alert('Что-то пошло не так. Попробуйте позже.');
} finally {
// Этот блок выполнится в любом случае
console.log('Попытка завершена');
}
Блок finally полезен для очистки ресурсов, например, закрытия соединения или сброса индикатора загрузки.
Где ловить ошибки: стратегии и уровни
Обработка ошибок может быть на разных уровнях: глобальном, уровне функции или даже отдельной операции. Важно выбрать правильный уровень, чтобы не дублировать код и не пропустить важное.
Глобальный обработчик — это последняя линия обороны. В браузере можно использовать window.onerror или window.addEventListener('error'). В Node.js — process.on('uncaughtException'). Но помните: после необработанного исключения приложение может находиться в нестабильном состоянии, поэтому лучше завершить процесс и перезапустить его.
// Глобальный перехват в браузере
window.addEventListener('error', function(event) {
// Отправляем ошибку на сервер для анализа
fetch('/log-error', {
method: 'POST',
body: JSON.stringify({
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
stack: event.error ? event.error.stack : null
})
});
// Не подавляем ошибку, чтобы она осталась в консоли
return false;
});
На уровне функции используйте try...catch вокруг рискованных операций: сетевые запросы, работа с DOM, парсинг данных.
Асинхронность: промисы и async/await
С асинхронным кодом всё сложнее. Ошибки в промисах не ловятся обычным try...catch, если не использовать await. Посмотрите на пример:
// Плохо: ошибка не будет поймана
function fetchData() {
fetch('/api/data')
.then(response => response.json())
.then(data => console.log(data));
}
try {
fetchData();
} catch (error) {
console.log('Эта строка не выполнится');
}
Чтобы поймать ошибку, нужно либо добавить .catch() в цепочку промисов, либо использовать async/await с try...catch.
// Хорошо: с async/await
async function fetchData() {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Не удалось загрузить данные:', error);
}
}
fetchData();
Всегда проверяйте response.ok — fetch не отклоняет промис при статусах 4xx и 5xx.
Создание собственных ошибок и их обработка
Иногда полезно выбрасывать свои ошибки, чтобы отделить их от системных и обрабатывать по-разному. Для этого можно расширить класс Error.
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field;
}
}
function validateUser(user) {
if (!user.name) {
throw new ValidationError('Имя обязательно', 'name');
}
if (!user.email.includes('@')) {
throw new ValidationError('Некорректный email', 'email');
}
}
try {
validateUser({ name: '', email: 'test' });
} catch (error) {
if (error instanceof ValidationError) {
console.log(`Ошибка в поле ${error.field}: ${error.message}`);
} else {
console.error('Неизвестная ошибка:', error);
}
}
Такой подход позволяет централизованно обрабатывать разные типы ошибок и показывать пользователю конкретные подсказки.
Логирование и мониторинг
Даже самая лучшая обработка ошибок бесполезна, если вы не знаете о них. Настройте логирование: отправляйте ошибки на сервер, используйте сервисы вроде Sentry или LogRocket. Но не логируйте всё подряд — это создаст шум.
Вот пример функции для отправки ошибок:
function logError(error, context = {}) {
// В разработке выводим в консоль
if (process.env.NODE_ENV === 'development') {
console.error('Ошибка:', error, 'Контекст:', context);
}
// В продакшене отправляем на сервер
fetch('/api/log', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: error.message,
stack: error.stack,
context,
timestamp: new Date().toISOString()
})
}).catch(() => {
// Если не удалось отправить лог, ничего не делаем
});
}
Практические советы и лайфхаки
- Используйте линтеры (ESLint) с правилами для отлова потенциальных ошибок, например, no-undef, no-unused-vars.
- Пишите тесты — они помогают выявить ошибки до продакшена.
- Не злоупотребляйте try...catch — оборачивайте только те участки, где действительно может возникнуть ошибка.
- Обрабатывайте ошибки в промисах — всегда добавляйте .catch() или используйте async/await с try...catch.
- Документируйте возможные ошибки — это поможет другим разработчикам.
Сравним популярные подходы к обработке ошибок:
| Подход | Плюсы | Минусы |
|---|---|---|
| try...catch | Простота, явное указание блока | Не работает для асинхронных операций без await |
| .catch() у промисов | Работает с цепочками промисов | Легко забыть добавить |
| Глобальные обработчики | Ловят всё, что не поймано | Не дают контекста, могут скрыть проблемы |
| Собственные классы ошибок | Гибкость, типизация | Требует дополнительного кода |
Заключение
Обработка ошибок — это не просто «обернуть всё в try...catch». Это стратегия, которая делает приложение предсказуемым и устойчивым. Начните с малого: добавьте глобальный обработчик, проверяйте ответы fetch, создавайте собственные типы ошибок для бизнес-логики. И не забывайте логировать — иначе вы никогда не узнаете о проблемах.
Помните: ошибки будут всегда. Ваша задача — сделать так, чтобы они не превращались в катастрофу.
Студия WNDER



