Обработка ошибок в JavaScript: try/catch и throw
Содержание
О проверке кода. Примеры не запускались — в отличие от материалов по Node.js с фактическим выводом. Говорим прямо, а не ставим бейдж «проверено».
Ошибки в программе неизбежны: сеть отвалилась, данные не те, файла нет. Вопрос не в том, будут ли ошибки, а в том, как программа на них реагирует. Разберём инструменты JavaScript для этого.
try/catch: основа
try {
const data = JSON.parse(input); // может бросить, если input не JSON
console.log(data.name);
} catch (error) {
console.error('Не разобрать JSON:', error.message);
}
Код в try выполняется как обычно. Если внутри возникает ошибка, выполнение try немедленно прерывается, и управление переходит в catch с объектом ошибки. Если ошибки нет — catch пропускается целиком.
Важно: try/catch ловит только синхронные ошибки внутри блока. Ошибка в отложенном коде (setTimeout, колбэк) сюда не попадёт — про асинхронность ниже.
throw: бросить ошибку
function divide(a, b) {
if (b === 0) {
throw new Error('Деление на ноль');
}
return a / b;
}
try {
divide(10, 0);
} catch (e) {
console.log(e.message); // "Деление на ноль"
}
throw останавливает функцию и передаёт ошибку вверх по стеку вызовов, пока её не поймает ближайший catch. Если не поймает никто — программа падает (в браузере — ошибка в консоли, в Node.js — краш процесса).
Бросайте объект Error, а не строку:
throw new Error('текст'); // ✅ есть message, name, stack
throw 'текст'; // ❌ нет стека вызовов, ломает единообразие
У Error есть stack — стек вызовов, показывающий, где именно всё сломалось. throw 'строка' этого лишает и заставляет catch гадать, что прилетело. Единообразие важнее краткости.
finally: выполнится всегда
function readData(file) {
const handle = open(file);
try {
return parse(handle); // даже с return...
} catch (e) {
return null;
} finally {
handle.close(); // ...finally всё равно закроет файл
}
}
- 1 tryВыполняется код. Если всё ок — сразу к finally
- 2 Ошибка?Если бросок — прыжок в catch с объектом ошибки
- 3 catchОбработка: логируем, восстанавливаемся или пробрасываем
- 4 finallyВыполняется ВСЕГДА — очистка ресурсов
finally выполняется в любом случае — при успехе, при ошибке, даже если в try или catch был return. Сюда кладут освобождение ресурсов: закрыть файл, соединение, убрать спиннер загрузки. Это гарантия, что уборка произойдёт независимо от исхода.
Свои классы ошибок
Для разных ситуаций полезно различать типы ошибок:
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field;
}
}
try {
throw new ValidationError('Пустое имя', 'name');
} catch (e) {
if (e instanceof ValidationError) {
console.log(`Поле ${e.field}: ${e.message}`); // осмысленная реакция
} else {
throw e; // не наша ошибка — пробрасываем дальше
}
}
Свой класс ошибки (наследник Error) позволяет отличать ожидаемые ошибки (валидация, «не найдено») от неожиданных и реагировать по-разному. Проверка через instanceof — стандартный приём. Чужие ошибки, которые вы не умеете обрабатывать, пробрасывайте дальше через throw e.
Ошибки в async/await
Асинхронный код ловят тем же try/catch:
async function loadUser(id) {
try {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (e) {
console.error('Не загрузить пользователя:', e.message);
return null;
}
}
Отклонённый промис внутри await превращается в исключение, которое ловит обычный catch. Это главное удобство async/await по сравнению с .catch() у промисов: обработка ошибок выглядит как в синхронном коде.
Тонкость: fetch не бросает на HTTP-ошибках вроде 404 — только на сетевых сбоях. Статус проверяют вручную через res.ok, как в примере.
Антипаттерны
❌ Так делать нельзя
Прячет проблемы
- Пустой
catch {}— глотает ошибку молча catch (e) {}без логированияthrow 'строка'— без стека- Ловить всё подряд и игнорировать
✅ Правильно
Ошибка видима и обработана
- Обработать осмысленно
- Или залогировать
- Или пробросить дальше
throw e - Различать типы через
instanceof
Худшее, что можно сделать с ошибкой — проглотить её. Пустой catch {} оставляет программу в сломанном состоянии, а вы не узнаете о баге, пока пользователь не пожалуется. С ошибкой делают одно из трёх: осмысленно обрабатывают, логируют или пробрасывают дальше тому, кто умеет обработать. Молча игнорировать — нельзя.
Смежные темы: промисы и async/await — асинхронные ошибки; классы — свои типы ошибок; обработка ошибок в Node.js. Полный список — в уроках JavaScript.