Исключения — один из немногих примеров удачного использования наследования. В этом уроке мы научимся создавать свои исключения и перехватывать их.
Обычно исключения используются так. Ближе к началу программы стоит конструкция try/catch, которая ловит исключения и показывает пользователю адекватное сообщение:
try {
doSomethingDangerous();
} catch (e) {
console.log(e);
}
Но как понять, что случилось? Иногда это важно. Разные ошибки могут приводить к разному поведению программы. Кроме того, не все ошибки требуют обработки в текущем месте программы.
Разделять ошибки можно с помощью разных классов наследующихся от класса Error.
// Такой способ работает, только если не используется Babel
class MyError extends Error {}
class FirstError extends MyError {}
class SecondError extends MyError {}
Причем в отличие от других примеров наследования, в исключениях редко нужно добавлять или изменять поведение. Основная цель использования наследования исключений — описание всех возможных типов ошибок.
Теперь посмотрим как этим можно воспользоваться. В теории мы можем написать такой код:
try {
// Какой-то код, который может выбросить исключение
} catch (e) {
if (e instanceof MyError) {
// Обрабатываем MyError
} else if (e instanceof FirstError) {
// Обрабатываем FirstError
} else if (e instanceof SecondError) {
// Обрабатываем SecondError
}
// Во всех остальных случаях, например, бросаем исключение снова
throw e;
}
Обработка любого базового исключения автоматически влечет за собой обработку всех наследников текущего класса. Например, если в блоке catch перехватывать в условии MyError, то этот блок поймает объекты этого класса и объекты всех наследников. Если будет перехвачена ошибка класса FirstError
, то блок с проверкой FirstError
никогда не выполнится, так как условие e instanceof MyError
в первом блоке будет истинным в этом случае. То же самое будет и с SecondError
. Чтобы избежать такого поведения, мы можем поставить проверку FirstError
и SecondError
перед MyError
, либо делать проверку имени класса e.constructor.name === 'FirstError'
.
Практически в любом языке программирования есть негласное правило о том, как работать с иерархиями исключений. Любая программа должна определять свое собственное высокоуровневое исключение, которое наследуется от Error. Все остальные исключения библиотеки наследуются от него. Такой подход позволяет изолировать обработку ошибок конкретной библиотеки буквально одним блоком catch.
Но не все библиотеки в JavaScript используют наследование. Дело в том, что до стандарта es6 не было классов и простого способа создания «наследников» от конструктора Error. И, так как многие библиотеки все еще реализуются на старом стандарте, они решают проблему определения типа ошибки с помощью обычного свойства:
import axios from 'axios';
try {
client.get('https://ru.hexlet.io');
} catch (e) {
if (e.isAxiosError) {
// Сюда попадут все ошибки библиотеки axios
}
// обработка остальных ошибок
}
Блок finally
В некоторых ситуациях бывает нужно продолжить работу независимо от того, возникло исключение или нет. Используя только try/catch, эту задачу нельзя выполнить без дублирования. Придется размещать код как после всей конструкции try/catch, так и в каждом блоке catch.
Это привело к тому, что саму конструкцию расширили, добавив в нее блок finally. Этот блок вызывается в самом конце и в любом случае:
try {
// Какой-то код
} catch (e) {
// Делаем что-нибудь одно
} finally {
// Вызовется в любом случае
}
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.