Обработка ошибок с помощью try...catch
Обработка ошибок с помощью try...catch
Технический фундамент try...catch
try...catch работает только для исключений, которые возникают во время выполнения кода внутри try. Это значит:
- синтаксические ошибки в самом файле не "лечатся"
catch; - после выброса ошибки оставшийся код блока
tryне выполняется; - управление переходит в ближайший подходящий
catch; - обработка должна быть точечной, иначе трудно локализовать причину.
Инженерный подход: оборачивать только рискованную операцию, а не весь сценарий целиком.
Что решает try...catch в реальном коде
try...catch нужен, когда ты ожидаешь, что операция может упасть, и хочешь контролируемо обработать сбой: показать пользователю понятное сообщение, вернуть fallback, записать лог и продолжить работу приложения. Без этого ошибка может оборвать выполнение важной части сценария.
Ключевой момент: try...catch не делает код "непадающим". Он дает тебе точку, где ты можешь осознанно решить, что делать с ошибкой.
Проверь себя: в чем разница между "поймать ошибку" и "исправить причину ошибки"?
Базовый синтаксис без лишней магии
try {
const data = JSON.parse('{"name":"Ann"}');
console.log(data.name);
} catch (error) {
console.log('Не удалось разобрать JSON:', error.message);
}
Как это работает по шагам:
- код в
tryвыполняется обычно; - если внутри
tryвыбрасывается ошибка, оставшаяся частьtryпропускается; - управление переходит в
catch; - в
catchты получаешь объект ошибки (error) и решаешь, что делать.
Здесь часто путаются: catch сработает только если ошибка действительно произошла. Если все в порядке, блок catch не выполняется.
Проверь себя: что выведется, если в примере JSON корректный?
Где try...catch особенно полезен
Типичные сценарии:
- разбор JSON из
localStorageили API; - преобразование пользовательского ввода;
- вызов кода, который может бросить исключение;
- интеграции с внешними библиотеками.
Мини-сценарий из продукта: ты читаешь сохраненные настройки из localStorage. Если строка повреждена, приложение не должно падать целиком.
function loadSettings(raw) {
try {
const parsed = JSON.parse(raw);
return parsed;
} catch (error) {
return { theme: 'light', language: 'ru' };
}
}
console.log(loadSettings('{"theme":"dark"}')); // { theme: 'dark' }
console.log(loadSettings('broken-json')); // fallback
Смотри, что важно: fallback должен быть осмысленным. Не любой "дефолт", а такой, с которым пользователь может продолжить работу.
Важно: try...catch и асинхронность
try...catch ловит только синхронные ошибки, которые произошли внутри блока try прямо сейчас.
Если ошибка случится позже (таймер, обработчик события, промис), внешний try...catch ее не поймает.
Например, это не поймает ошибку:
try {
setTimeout(() => {
JSON.parse('{bad json}');
}, 0);
} catch (error) {
console.log('Не сработает');
}
Правильно: ловить внутри асинхронного колбэка или использовать await внутри try (для async функций):
setTimeout(() => {
try {
JSON.parse('{bad json}');
} catch (error) {
console.log('Поймали внутри колбэка');
}
}, 0);
async function safeParseAsync(raw) {
try {
const text = await Promise.resolve(raw);
return JSON.parse(text);
} catch {
return null;
}
}
finally: код, который выполнится в любом случае
Иногда тебе нужно гарантированно выполнить действие и при успехе, и при ошибке: выключить loader, закрыть ресурс, сбросить временный статус.
function safeParse(raw) {
let result = null;
try {
result = JSON.parse(raw);
} catch (error) {
result = null;
} finally {
console.log('parse attempt finished');
}
return result;
}
finally выполняется всегда после try/catch. Это удобно для "уборки" состояния.
Проверь себя: почему скрывать важную бизнес-логику внутри finally обычно плохая идея?
Частая ошибка: слишком широкий try
Новички часто оборачивают в try огромный кусок кода. В итоге сложно понять, где именно произошла проблема.
Плохой подход:
- один
tryна 50 строк; - в
catchтолько "что-то пошло не так"; - причины теряются.
Лучше:
- оборачивать только рискованную операцию;
- в
catchлогировать контекст (какие входные данные, какой шаг); - возвращать предсказуемый результат.
function getUserName(rawUserJson) {
try {
const user = JSON.parse(rawUserJson);
return user.name ?? 'Гость';
} catch (error) {
console.error('getUserName parse error:', rawUserJson);
return 'Гость';
}
}
Анти-провал: если ловишь ошибку, не теряй полезный контекст, иначе дебаг будет почти вслепую.
Когда нельзя "молчать" в catch
Иногда разработчики делают пустой catch, чтобы убрать падение. Это опасно: проблема остается, просто становится невидимой.
try {
riskyOperation();
} catch (error) {
// Плохо: ошибка исчезла, но система в непонятном состоянии
}
Дополнительный пример: повторный throw после логирования, если ошибку нельзя безопасно погасить.
function parseOrThrow(raw) {
try {
return JSON.parse(raw);
} catch (error) {
console.error('parseOrThrow failed:', error.message);
throw error;
}
}
Если ты решил обработать ошибку, зафиксируй минимум:
- что за операция;
- почему не удалось;
- какой fallback применен.
Проверь себя: почему "тихий" catch может привести к более дорогому багу позже?
Микро-сценарии применения
- Валидация JSON-конфига перед рендером страницы.
- Безопасный парсинг фильтров из URL-параметров.
В обоих случаях цель одна: не ронять интерфейс из-за одной проблемной строки данных.
Краткий итог
try...catchнужен для контролируемой обработки ожидаемых сбоев.- Лови ошибки точечно, а не гигантскими блоками.
- В
catchсохраняй контекст и возвращай понятный fallback. finallyподходит для гарантированной очистки состояния.- Никогда не глуши ошибки молча: это откладывает проблему и усложняет отладку.