try..catch: перехват ошибок
Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.
Ошибка в JavaScript — не обязательно конец программы. try..catch — это способ сказать движку: «я знаю, что вот здесь может сломаться, дай мне разобраться самому».
Обычный скрипт выполняется как список поручений: строка за строкой. Ошибка рвёт этот список — движок останавливается ровно там, где споткнулся, и остальное не выполняется. try..catch ставит на пути ошибки сеть: всё, что упало внутри try, попадает в catch, а программа идёт дальше.
В catch приходит объект ошибки. У него есть name — тип поломки, message — человеческое описание и stack — путь, по которому движок дошёл до беды. Текст message пишут разработчики движка, поэтому в разных браузерах он может отличаться словами; name и тип — одинаковые везде.
Скобки у catch можно не писать вовсе: catch { ... }. Так делают, когда сам объект ошибки не нужен — важен только факт падения.
try..catch работает только с ошибками времени выполнения — теми, что случаются, когда код уже запущен. Если в файле забыта скобка, движок не сможет даже разобрать текст программы и не запустит из него ни строчки: ловить будет некому, потому что сам try не успеет появиться. Правило простое — сначала код должен уметь запуститься, и только потом его можно защищать.
Второе ограничение: «неправильный ответ» — это не ошибка. Number("двенадцать") вернёт NaN, обращение к несуществующему свойству вернёт undefined, и никакого исключения не будет. catch в таких местах просто не сработает — проверять значения приходится руками.
| Тип ошибки | Когда возникает |
|---|---|
| ReferenceError | Обращение к переменной, которой нет |
| TypeError | Значение не того типа: null.length, вызов не-функции, запись в const |
| SyntaxError | Строку не удалось разобрать — например, JSON.parse на мусоре |
| RangeError | Значение вне допустимого диапазона: new Array(-1), слишком глубокая рекурсия |
| Error | Базовый тип; именно его чаще всего бросают вручную |
Блок finally выполняется всегда: и когда всё прошло гладко, и когда сработал catch, и даже когда внутри try стоит return. Это место для уборки — снять флаг загрузки, остановить таймер, закрыть соединение. Уборка не должна зависеть от того, повезло нам или нет.
Обратите внимание на порядок: функция уже вычислила, что вернуть, но пока не отдала результат наружу — finally вклинивается ровно в этот момент. Поэтому «после return ничего не выполняется» — правило с исключением, и это исключение называется finally.
Тогда он перебьёт результат из try — и заодно проглотит ошибку, если она была. Формально это работает, но читателю кода приходится держать в голове слишком многое, поэтому так лучше не писать.
Ошибки бросает не только движок — их можно бросать самому оператором throw. Технически бросить разрешено что угодно, хоть строку, хоть число, но так делать не стоит: у объекта Error есть name, message и стек вызовов, а у строки — ничего. Пишите throw new Error("...") и выбирайте встроенный подтип по смыслу: TypeError для не того типа, RangeError для недопустимого значения.
Хорошее сообщение отвечает на вопрос «что именно пошло не так», а не «всё плохо». Сравните: «ошибка данных» и «возраст должен быть числом» — второе можно чинить, не заглядывая в исходники.
Когда в проекте несколько источников сбоев, удобно завести собственные классы: тогда в catch можно разбирать ошибки по типу через instanceof, а не сравнивать текст сообщений. Сейчас достаточно увидеть приём целиком; отдельная статья «Свои классы ошибок» разбирает и name, и cause, и поля с подробностями.
try..catch — синхронный сторож. Он охраняет ровно тот отрезок времени, пока выполняются строки внутри блока. Колбэк setTimeout запускается позже, когда try давно закрыт, — поймать оттуда ошибку он уже не может, как зонт не спасает от дождя, который пойдёт завтра.
С промисами та же история. Вызов асинхронной функции без await просто возвращает промис и мгновенно идёт дальше; ошибка внутри превращается в отклонение промиса и пролетает мимо catch. Лечится это одним словом — await внутри try — или методом .catch() на самом промисе.
Проверка простая: если ошибка может случиться не сейчас, а «когда-нибудь потом», обычный try её не увидит. Нужно либо дождаться результата через await, либо ловить прямо в том месте, где код продолжится, — внутри самого колбэка.
Самый вредный catch — пустой. Он не чинит поломку, а прячет её: программа делает вид, что всё хорошо, и продолжает работать с несуществующими данными. Такой баг всплывёт через три экрана и на совсем другой строке.
Правило одно: в catch вы либо действительно справляетесь с проблемой — подставляете запасное значение, показываете сообщение, пишете в лог, — либо честно пробрасываете ошибку дальше через throw err. И ловите только то, что умеете обработать: чужая ошибка должна лететь наверх, к тому, кто про неё знает.
- try..catch перехватывает ошибки времени выполнения: упало внутри try — управление уходит в catch, и скрипт продолжает работать.
- Синтаксическая ошибка в самом файле не ловится: движок не разберёт такой код и не запустит из него ничего. И «неправильный результат» вроде NaN — тоже не ошибка.
- finally выполняется всегда: после успеха, после catch и даже перед тем, как функция отдаст значение из return.
- throw new Error("...") бросает свой сбой со своим сообщением; для разных ситуаций удобны встроенные подтипы или собственные классы ошибок.
- Асинхронную ошибку нужно дождаться — через await внутри try или через .catch(); а пустой catch недопустим: либо обработайте, либо пробросьте дальше.
1/3Что выведет код: try { console.log("A"); throw new Error("!"); console.log("B"); } catch { console.log("C"); } finally { console.log("D"); }
2/3Поймает ли catch ошибку здесь: try { setTimeout(function () { null.x; }, 100); } catch (err) { ... }
3/3Функция возвращает значение из try, а в finally стоит console.log. Что произойдёт?
safeParse: разбор JSON без падений
Напишите функцию safeParse(raw, fallback): она пытается разобрать строку через JSON.parse и возвращает результат, а если строка битая — возвращает fallback вместо того, чтобы уронить программу.
Весь разбор заворачиваем в try, а запасное значение возвращаем из catch. Никаких проверок строки вручную не нужно — JSON.parse сам сообщит о проблеме исключением.
Обратите внимание: здесь catch не пустой — он делает осмысленную работу, подставляет запасное значение. Если бы в проекте важно было знать о битых данных, сюда стоило бы добавить и запись в лог.
divide, который сам сообщает о проблеме
Допишите функцию divide(a, b) так, чтобы она бросала TypeError, если хотя бы один аргумент не число, и RangeError при делении на ноль. Затем вызовите её в try..catch и выведите тип и сообщение ошибки.
Проверки идут до вычисления: сначала типы, потом диапазон. Так функция падает с внятным сообщением, а не возвращает молчаливые NaN и Infinity.
Разные типы ошибок выбраны не для красоты: в catch вызывающий код сможет отличить RangeError от TypeError через instanceof и отреагировать по-разному.