throw и try…catch
Чему вы научитесь:
- бросать ошибку через
throwи ловить её вtry…catch; - объяснять, как ошибка проходит по цепочке вызовов до ближайшего
catch; - наводить порядок в
finallyпри любом исходе; - различать ошибки по
nameиinstanceofи создавать свои классы ошибок.

Представьте цепочку поручений. Начальник просит помощника подготовить отчёт, а помощник поручает стажёру посчитать цифры. Стажёру дали пустую таблицу, и посчитать он ничего не может.
Выдумывать ответ стажёр не станет. Он бросает наверх записку с причиной: «таблица пустая». Записка летит обратно по цепочке, и её ловит первый, кто заранее приготовился к такому случаю. Если не приготовился никто, вся работа останавливается.
Так в JavaScript устроены ошибки. Ошибки из прошлых тем — ReferenceError, TypeError, RangeError — останавливали программу. Функция может остановить её и сама, если получила значение, с которым работать нельзя. Деление на ноль в JavaScript ошибкой не считается и даёт Infinity, поэтому функция divide проверяет делитель:
function divide(a, b) {
if (b === 0) {
throw new Error("division by zero");
}
return a / b;
}
console.log(divide(6, 3)); // 2
console.log(divide(1, 0)); // ✗
console.log("end");Error: division by zeroДавайте разберём, что случилось:
divide(6, 3)прошла проверку и вернула2, и программа его напечатала.divide(1, 0)увидела ноль в делителе и бросила ошибку.- Эту ошибку никто не поймал, и программа остановилась, как от
ReferenceErrorв первой теме. - Строка
endтак и не напечатана.
Бросить ошибку
Выражение new Error("division by zero") создаёт объект ошибки — ту самую записку. Error — встроенный класс, а строка в скобках становится текстом ошибки.
Оператор throw бросает этот объект. Вот что при этом происходит:
- функция прерывается на этой строке, как на
return, но значения не возвращает; - ошибка выходит в код, который вызвал функцию, и прерывает его тоже;
- так она идёт дальше, вызов за вызовом, пока её кто-нибудь не поймает.
Так же устроены ошибки прошлых тем: TypeError и ReferenceError бросает сам JavaScript. Их никто не ловил, поэтому программа останавливалась.
Поймать ошибку
Код, который может бросить ошибку, помещают в блок try. Если в нём брошена ошибка, остаток блока пропускается, и выполнение переходит в блок catch. Там ошибка поймана: программа не останавливается и после catch идёт дальше.
function divide(a, b) {
if (b === 0) {
throw new Error("division by zero");
}
return a / b;
}
try {
console.log(divide(6, 3)); // 2
console.log(divide(1, 0));
console.log("skipped");
} catch (e) {
console.log(e.message); // division by zero
}
console.log("end"); // endРазберём по шагам:
divide(6, 3)возвращает2, и первая строка блокаtryего печатает.divide(1, 0)бросает ошибку, и строкаskippedне выполняется.- Выполнение переходит в
catch, и тот печатает текст ошибки. - После
catchпрограмма идёт дальше и печатаетend.
В скобках после catch стоит имя e: в него попадает брошенный объект. Свойство message этого объекта хранит текст, переданный в new Error. Если блок try прошёл без ошибок, блок catch не выполняется вовсе.
throw работает одинаково и внутри функции, и прямо в блоке try: строки блока после него пропускаются.
Путь ошибки по вызовам
Вспомните стек вызовов из темы о рекурсии: каждый незавершённый вызов занимает в нём свою запись. Брошенная ошибка снимает эти записи одну за другой, сверху вниз. В каждой функции, через которую она проходит, оставшиеся строки пропускаются. Остановить ошибку может только try…catch, внутри которого сделан вызов.
Нажимайте «Следующий шаг» и следите за шагом, на котором check бросает ошибку: рамки check и double исчезают сразу, а строка return v * 2 так и не выполняется.
1function check(n) {2 if (n < 0) {3 throw new Error("negative");4 }5 return n;6}7 8function double(n) {9 const v = check(n);10 return v * 2;11}12 13try {14 console.log(double(3));15 console.log(double(-1));16 console.log("skipped");17} catch (e) {18 console.log(e.message);19}20console.log("end");
Внутри try программа вызывает double(3) и ждёт результат.
Здесь аналогия с запиской перестаёт работать. Человек в цепочке мог бы сам решить, ловить записку или нет. У функции без try выбора нет: ошибка прерывает её сама.
Этот вопрос часто задают на собеседовании: «Что будет с ошибкой, брошенной глубоко во вложенном вызове?» Она поднимается по стеку вызовов до ближайшего try…catch, а если его нет, программа останавливается.
Частая ошибка
Переменную объявили внутри try, а читают после него:
try {
const result = divide(6, 3);
} catch (e) {
console.log(e.message);
}
console.log(result); // ✗ReferenceError: result is not definedБлок try — такой же блок {}, как у if. А const и let существуют только в своём блоке. Объявите переменную через let до try, а внутри только присвойте ей значение: let result;, затем result = divide(6, 3);.
Главное
throwбросает объект ошибки, напримерnew Error("текст"), и прерывает функцию.- Ошибка поднимается по стеку вызовов до ближайшего
catch, пропуская все строки послеthrow. - В
catch (e)попадает брошенный объект, аe.messageхранит его текст. - Непойманная ошибка останавливает программу.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.