Перейти к содержимому
Теория · Ошибки · урок 1 из 10

throw и try…catch

Чему вы научитесь

Чему вы научитесь:

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

Представьте цепочку поручений. Начальник просит помощника подготовить отчёт, а помощник поручает стажёру посчитать цифры. Стажёру дали пустую таблицу, и посчитать он ничего не может.

Выдумывать ответ стажёр не станет. Он бросает наверх записку с причиной: «таблица пустая». Записка летит обратно по цепочке, и её ловит первый, кто заранее приготовился к такому случаю. Если не приготовился никто, вся работа останавливается.

Так в JavaScript устроены ошибки. Ошибки из прошлых тем — ReferenceError, TypeError, RangeError — останавливали программу. Функция может остановить её и сама, если получила значение, с которым работать нельзя. Деление на ноль в JavaScript ошибкой не считается и даёт Infinity, поэтому функция divide проверяет делитель:

JavaScript
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");
TEXT
Error: division by zero

Давайте разберём, что случилось:

  1. divide(6, 3) прошла проверку и вернула 2, и программа его напечатала.
  2. divide(1, 0) увидела ноль в делителе и бросила ошибку.
  3. Эту ошибку никто не поймал, и программа остановилась, как от ReferenceError в первой теме.
  4. Строка end так и не напечатана.

Бросить ошибку

Выражение new Error("division by zero") создаёт объект ошибки — ту самую записку. Error — встроенный класс, а строка в скобках становится текстом ошибки.

Оператор throw бросает этот объект. Вот что при этом происходит:

  • функция прерывается на этой строке, как на return, но значения не возвращает;
  • ошибка выходит в код, который вызвал функцию, и прерывает его тоже;
  • так она идёт дальше, вызов за вызовом, пока её кто-нибудь не поймает.

Так же устроены ошибки прошлых тем: TypeError и ReferenceError бросает сам JavaScript. Их никто не ловил, поэтому программа останавливалась.

Поймать ошибку

Код, который может бросить ошибку, помещают в блок try. Если в нём брошена ошибка, остаток блока пропускается, и выполнение переходит в блок catch. Там ошибка поймана: программа не останавливается и после catch идёт дальше.

JavaScript
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

Разберём по шагам:

  1. divide(6, 3) возвращает 2, и первая строка блока try его печатает.
  2. divide(1, 0) бросает ошибку, и строка skipped не выполняется.
  3. Выполнение переходит в catch, и тот печатает текст ошибки.
  4. После catch программа идёт дальше и печатает end.

В скобках после catch стоит имя e: в него попадает брошенный объект. Свойство message этого объекта хранит текст, переданный в new Error. Если блок try прошёл без ошибок, блок catch не выполняется вовсе.

throw работает одинаково и внутри функции, и прямо в блоке try: строки блока после него пропускаются.

Путь ошибки по вызовам

Вспомните стек вызовов из темы о рекурсии: каждый незавершённый вызов занимает в нём свою запись. Брошенная ошибка снимает эти записи одну за другой, сверху вниз. В каждой функции, через которую она проходит, оставшиеся строки пропускаются. Остановить ошибку может только try…catch, внутри которого сделан вызов.

Нажимайте «Следующий шаг» и следите за шагом, на котором check бросает ошибку: рамки check и double исчезают сразу, а строка return v * 2 так и не выполняется.

ВживуюПуть ошибки по стеку вызововшаг 1 из 10 · рамок: 1

Внутри try программа вызывает double(3) и ждёт результат.

Ошибка снимает рамки, пока не дойдёт до try

Здесь аналогия с запиской перестаёт работать. Человек в цепочке мог бы сам решить, ловить записку или нет. У функции без try выбора нет: ошибка прерывает её сама.

Этот вопрос часто задают на собеседовании: «Что будет с ошибкой, брошенной глубоко во вложенном вызове?» Она поднимается по стеку вызовов до ближайшего try…catch, а если его нет, программа останавливается.

Частая ошибка

Переменную объявили внутри try, а читают после него:

JavaScript
try {
    const result = divide(6, 3);
} catch (e) {
    console.log(e.message);
}
console.log(result); // ✗
TEXT
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 и подпишись на канал — откроются все уроки.

Войти другим способом