Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Исключения JS: Обработка ошибок

Исключения — это механизм для работы с ошибками в современных языках программирования. В отличие от кодов возврата, он сложный для понимания, так как с первыми понятно, как работать, и какие там есть варианты. Еще он требует поддержки языка, которая включает новые синтаксические конструкции.

В реальной жизни исключения используют не совсем правильно. В этом уроке мы разберем, для чего они созданы, как с ними работать, и в каких случаях их нужно применять.

Stack

Существует абстрактный тип данных — stack. Это некая стопка:

Стек

Это детские игрушки, в которых на палочку по очереди насаживаются кольца. При этом снять их можно только в обратном порядке. Это и есть stack.

Еще часто подразумевают stack, когда говорят про магазин в автоматах или пистолетах — кто заходит первым, тот выходит последним.

Получается, что у stack такая особенность: кто первый заходит, тот позже и выходит. Это стопка тарелок или блинов — их тоже обычно забирают в обратной последовательности.

В программировании stack встречается довольно часто. Например, stack вызовов — что мы видим в результате ошибки, которая произошла в коде:

Стек вызовов

Здесь показан пример из JavaScript. На верхнем уровне ошибка, которая произошла в файле на девятой строчке. Ниже в обратном порядке идут все вызовы. То есть мы начинали снизу и постепенно проваливались глубже. Функция вызывала внутри себя другую функцию и так далее, пока не добралась до определенного места.

Получается, когда происходит ошибка, идет раскручивание стека в обратном порядке. Так мы видим, что произошло, и какие элементы задействовались в процессе возврата ошибки.

Представим, что глубоко в стеке вызовов происходит чтение файла. И в итоге мы получаем ошибку:

// function body...
const [data, err] = files.readFileSync(path);
if (err) {
  return err;
} else {
  // process
}

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

Представим, если это произошло на уровне десяти вызовов функций. Значит, что на каждом уровне выше нужно проверить этот же тип ошибки. И это должно будет повториться десять раз. При этом каждый раз, когда мы входим в глубину, могут появляться новые типы ошибок, которые уже свойственны этой функции.

Получается, что функция наверху, которая вызывает остальное рекурсивно, должна обрабатывать все возникающие ошибки. Обработать такую ситуацию будет сложно. В итоге мы просто перестанем понимать, так как на один вызов может приходиться десятки строк только обработки ошибок.

Исключительные ситуации

Большинство таких ситуаций называются исключительными — критическая ошибка, после которой невозможно восстановление до определенной точки в стеке вызовов. Например, когда нам нужно подняться на десять функций вверх.

Также исключительная ситуация всегда приводит к раскрутке стека. То есть нам не важны промежуточные вызовы, а нужно сразу подняться до того уровня, на котором мы можем обработать ошибку или отреагировать на нее. Нам не нужно ставить эту обработку на всех вызовах, а поставить ее только на самом верхнем. Например, где мы знаем, что сюда может прийти эта ошибка, и мы здесь хотим ее обработать.

Допустим, у нас происходят вызовы в стеке: main(), method1(), method2() и method3(). В результате где-то происходит ошибка:

Исключения

В этом случае исключения проходят сквозь стек в обратном порядке и раскручивают его. При этом они автоматически пропускают вызовы method1(), method2() и method3() и поднимаются до функции main(). То есть идет не возврат значения, а подъем некоторым способом. Он идет в обход стандартного пути работы программы. При этом во время процесса здесь нигде нет никакого знания о том, что есть исключения.

Здесь просто вызовы функций, которые ожидают, что все будет работать хорошо.

Когда мы поднимаемся до функции main(), на ее уровне происходит обработка всех ошибок.

Исключения в JavaScript

Рассмотрим синтаксис исключений в JavaScript, в большинстве языков он практически одинаковый:

const g = () => undefinedFunc();
const f = () => g();

// Пишем ключевое слово try и определяем блок
// Внутри делаем вызов, который может привести к исключению
try {
  f();
  // Описываем блок catch, куда ловим исключение
} catch (e) {
  console.log(e);
}

// ReferenceError: undefinedFunc is not defined
// at g (main.js:60:17)
// at f (main.js:61:17)
// at Object.<anonymous> (main.js:64:3)

Исключение — это объект, который можно, например, распечатать. Если сделать это, то можно увидеть типичный вывод ошибок в JavaScript. Получается, что в JavaScript практически все баги, которые мы запускаем, приводят к исключительным ситуациям.

В примере выше — это ReferenceError. То есть мы вызвали функцию f, которая внутри себя вызывает функцию g. А она вызывает функцию UndefinedFunс, которой не существует:

И в ошибке нам об этом сообщается — undefinedFunс is not defined. Дальше мы видим, что на строчке 60 была вызвана g, потом f и строку main.js:64 — то, с чего мы начали.

В этом примере мы получили исключение. Мы увидели stack в обратном порядке, а также, что внутри f нет обработки ошибок — там ничего нет. Мы просто вызываем внутреннюю функцию, и исключение пробрасывает это наверх до блока try catch.

В итоге этот код позволяет сократить необходимость обработки вложенных вызовов. При этом исключение здесь появляется автоматически — это делает интерпретатор. Но есть способ самостоятельно порождать эти исключения.

Возбуждение исключения

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

const obj = new Error(message);
throw obj;

Здесь мы создаем объект, в который передается сообщение об ошибке. После этого мы используем специальную конструкцию, которая называется throw, и после этого ей записывается какой-то объект.

На этом этапе больше ничего не происходит. Все последующие вызовы не будут учитываться. Будет происходить раскрутка stack до тех пор, пока JavaScript не наткнется на блок try catch, в который он передаст обработку.

Посмотрим, как с точки зрения JavaScript с использованием этого механизма должен выглядеть метод statSync():

statSync(path) {
  const parts = getPathParts(path);
  const current = this.tree.getDeepChild(parts);
  if (!current) {
    // Если не находим current, то берем ошибку и делаем бросание исключения
    const error = errors.code.ENOENT;
    throw new HexletFsError(error, path);
  }
  return current.getMeta().getStats();
}

В этом примере можно увидеть, что от URL можно наследоваться с определенными ограничениями. Также здесь видно, что мы немного поменяли интерфейс, потому что у нас по-другому идет передача ошибок и формирования.

Теперь тем, кто использует нашу библиотеку, необязательно на каждом уровне ставить эти проверки. Им достаточно на самом верхнем уровне проверить, что исключения вызываются, и сделать общую обработку.

Исключения нужно бросать в том случае, когда функция не способна выполнить, что она обещает. Это эмпирическое правило. Оно поможет понять, нужно ли использовать исключения в конкретном месте или нет.

Выводы

В этом уроке мы разобрали, для чего созданы исключения, как с ними работать, и в каких случаях их нужно применять. Мы узнали, что исключительная ситуация — это критическая ошибка, после которой невозможно восстановление до определенной точки в стеке вызовов. Она всегда приводит к раскрутке абстрактного типа данных — стека.

Также мы выяснили, что исключение — это объект, который можно распечатать. Если сделать это, то можно увидеть типичный вывод ошибок в JavaScript. Его нужно бросать в том случае, когда функция не способна выполнить, что она обещает.


Дополнительные материалы

  1. Коды возврата & исключения

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Об обучении на Хекслете

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»