В предыдущих уроках мы с вами изучили два места, в которых JavaScript выбрасывает исключение в случае каких-то проблем.
В вашем проекте, однако, могут быть и другие ситуации, которые для вас являются исключительными, а для JavaScript - нет. В таком случае мы можете создавать и выбрасывать свои, пользовательские исключения.
Давайте разберем синтаксис, необходимый для
этого. Для начала исключение нужно создать
с помощью команды new Error
, параметром
передав текст исключения:
new Error('текст исключения');
Затем это исключение нужно выбросить с помощью
команды throw
:
throw new Error('текст исключения');
Выбрасывание исключение заставляет JavaScript
считать, что случилась исключительная ситуация.
Это значит, что такое исключение можно отловить
с помощью конструкции try-catch
и
обработать нужным образом.
Давайте посмотрим на примере, как этим пользоваться. Пусть у нас есть функция, которая делит одно число на другое:
function div(a, b) {
return a / b;
}
Давайте будем считать, что деление на ноль
запрещено и при попытке сделать это должна
возникать исключительная ситуация. Для этого
будем проверять в функции, нет ли попытки
делить на 0
. Если нет - будем делить,
а если нет - будем бросать исключение:
function div(a, b) {
if (b !== 0) {
return a / b;
} else {
throw new Error('ошибка деления на ноль');
}
}
Давайте для начала просто попробуем поделить
на 0
, не перехватывая исключение:
alert( div(3, 0) );
В этом случае выполнение скрипта прервется
и в консоли появится ошибка с текстом 'ошибка
деления на ноль'
(проверьте). Давайте
теперь будем перехватывать нашу ошибку и
как-то ее обрабатывать:
try {
alert( div(3, 0) );
} catch (error) {
alert('вы пытаетесь делить на 0, что запрещено');
}
В JavaScript попытка извлечь корень из отрицательного числа не приводит к выбрасыванию исключения:
let result = Math.sqrt(-1);
console.log(result); // выведет NaN
Напишите свою функцию, которая будет извлекать корень из числа и при этом выбрасывать исключение, если корень извлекается из отрицательного числа.
Типы исключений
Давайте выбросим свое исключение и посмотрим, как будет вести себя объект с ошибкой в этом случае:
try {
throw new Error('текст исключения');
} catch (error) {
console.log(error.name); // 'Error'
console.log(error.message); // 'текст исключения'
}
Как вы видите, тип нашего исключения - 'Error'
.
Такой же тип будет у любых исключений, выброшенных
подобным образом. Это, однако, не всегда
будет удобно, так как, если у нас могут быть
несколько исключений, мы не сможем их отличить
друг от друга.
В JavaScript предусмотрено решение этой проблемы:
можно выбрасывать исключения не только типа
Error
, но и любого встроенного в JavaScript
типа ошибки, например, TypeError
,
SyntaxError
, RangeError
.
Давайте для примера выбросим исключение типа
SyntaxError
:
try {
throw new SyntaxError('текст исключения');
} catch (error) {
console.log(error.name); // 'SyntaxError'
console.log(error.message); // 'текст исключения'
}
Выбросите исключение с типом TypeError
.
Выбросите исключение с типом SyntaxError
и RangeError
. Поймайте эти исключения
с помощью одного блока try. В блоке catch
выведите разные сообщения об ошибке для исключений
разных типов.
Свои типы исключений
Количество встроенных в JavaScript исключений не такое большое и они чаще всего не могут удовлетворить все наши потребности в разных типах исключений. Поэтому в JavaScript встроена возможность создавать исключения со своим типом.
Существуют разные способы сделать это. Самый
простой - в throw
передать объект
с ключами name
и message
:
try {
throw {name: 'MyError', message: 'текст исключения'};
} catch (error) {
console.log(error.name); // 'MyError'
console.log(error.message); // 'текст исключения'
}
Выше я мы сделали функцию, выбрасывающую исключение при делении на ноль:
function div(a, b) {
if (b !== 0) {
return a / b;
} else {
throw new Error('ошибка деления на ноль');
}
}
Переделайте эту функцию так, чтобы она выбрасывала
исключение с каким-нибудь придуманными нами
типом, например, DivisionByZeroError
.
Выше вы делали функцию, выбрасывающую исключение при попытке извлечь корень из отрицательного числа. Переделайте вашу функцию так, чтобы она выбрасывала исключение с придуманным вами типом. Хорошо подумайте над названием исключения, чтобы это название было удачным.
Пример применения
Пусть при загрузке страницы сервер создает HTML код, в котором хранится название, цена и количество купленного продукта:
<div id="product" data-product="яблоко" data-price="1000" data-amount="5"></div>
Давайте сделаем функцию, которая будет принимать ссылку на элемент с продуктом и находить полную стоимость товара (цену умножать на количество):
function getCost(elem) {
return elem.dataset.price * elem.dataset.amount;
}
Найдем стоимость нашего продукта:
let product = document.querySelector('#product');
let cost = getCost(product);
alert(cost);
Предположим теперь следующую ситуацию: из-за какого-то сбоя на сервере он прислал нам товар, в котором отсутствует цена или количество (или оба сразу), например, вот так:
<div id="product" data-product="яблоко" data-price="1000"></div>
Если теперь попробовать посчитать стоимость товара, то результате на экран выведется NaN. Согласитесь, не очень информативно.
Получается, нам нужно как-то обезопасится от того, что будут отсутствовать нужные нам атрибуты. Это можно сделать двумя путями. Первый путь - это сказать, что это нормальное поведение и просто поверять ифами наличие нужных нам атрибутов:
function getCost(elem) {
if (elem.dataset.price !== undefined && elem.dataset.amount !== undefined) {
return elem.dataset.price * elem.dataset.amount;
} else {
return 0; // вернем что-нибудь, например, 0 или null или false
}
}
Второй вариант - это сказать, что отсутствие
атрибута data-price
или data-amount
- исключительная ситуация. В этом случае
мы будем выбрасывать исключение:
function getCost(elem) {
if (elem.dataset.price !== undefined && elem.dataset.amount !== undefined) {
return elem.dataset.price * elem.dataset.amount;
} else {
throw {
name: 'ProductCostError',
message: 'отсутствует цена или количество у продукта'
};
}
}
Какой из двух вариантов здесь уместнее применить - это выбор программиста. Он может считать проблему нормальной работой скрипта или исключительной ситуацией.
Пусть мы решили, что ситуация исключительная. Тогда код получения стоимости товара будет выглядеть вот так:
let product = document.querySelector('#product');
try {
let cost = getCost(product);
alert(cost);
} catch (error) {
// как-то реагируем на исключение
}
Переделайте мой код так, чтобы функция getCost
выбрасывала два типа исключений: если отсутствует
цена и если отсутствует количество. Хорошо
подумайте над названиями этих исключений.
В блоке catch выведите разные сообщения об
ошибке для исключений разных типов.
Еще пример применения
Пусть к нам откуда-то из внешнего мира приходит JSON с продуктом:
let json = '{"product": "яблоко", "price": 1000, "amount": 5}';
let product = JSON.parse(json);
alert(product.price * product.amount);
Вы уже знаете, что метод JSON.parse будет выбрасывать исключение, если JSON некорректный. Давайте поймаем это исключение:
try {
let json = '{"product": "яблоко", "price": 1000, "amount": 5}';
let product = JSON.parse(json);
alert(product.price * product.amount);
} catch (error) {
// как-то реагируем на исключение
}
Однако, может быть такое, что сам по себе JSON корректный, но не содержит нужных нам полей, например, нет поля с ценой:
let json = '{"product": "яблоко", "amount": 5}'; // нет цены
Давайте скажем, что это тоже исключительная ситуация и будем в таком случае выбрасывать свое пользовательское исключение:
try {
let json = '{"product": "яблоко", "amount": 5}';
let product = JSON.parse(json);
if (product.price !== undefined && product.amount !== undefined) {
alert(product.price * product.amount);
} else {
throw {
name: 'ProductCostError',
message: 'отсутствует цена или количество у продукта'
};
}
} catch (error) {
// как-то реагируем на исключение
}
Теперь блок catch будет получать два типа
исключений: либо JSON вообще некорректен,
и тогда будет исключение типа SyntaxError
,
либо JSON корректен, но не содержит нужных
нам полей, и тогда будет исключение типа
ProductCostError
.
Давайте в блоке catch будем отлавливать эти типы исключений:
try {
let json = '{"product": "яблоко", "amount": 5}';
let product = JSON.parse(json);
if (product.price !== undefined && product.amount !== undefined) {
alert(product.price * product.amount);
} else {
throw {
name: 'ProductCostError',
message: 'отсутствует цена или количество у продукта'
};
}
} catch (error) {
if (error.name == 'SyntaxError') {
alert('Некорректный JSON продукта');
} else if (error.name == 'ProductCostError') {
alert('У продукта отсутствует цена или количество');
}
}
Пусть к вам приходит JSON вот такого вида:
let json = `[
{
"name": "user1",
"age": 25,
"salary": 1000
},
{
"name": "user2",
"age": 26,
"salary": 2000
},
{
"name": "user3",
"age": 27,
"salary": 3000
}
]`;
Проверьте этот JSON на общую корректность при разборе, а после разбора проверьте, что в результате получается массив, а не что-то другое. Если в результате получается не массив - выбросите исключение.
Проброс исключений
Рассмотрим блок catch задачи о JSON продукта:
catch (error) {
if (error.name == 'SyntaxError') {
alert('Некорректный JSON продукта');
} else if (error.name == 'ProductCostError') {
alert('У продукта отсутствует цена или количество');
}
}
Как вы видите, мы ловим два запланированных
нами исключения и как-то реагируем на это.
Но что будет, если возникнет непредусмотренное
нами исключение другого типа? В этом случае
оно тоже попадет в блок catch
, но
никакой реакции на это не последует, так
как исключение с другим типом просто не попадет
ни в один из наших ифов.
Когда я говорю, что не будет никакой реакции, то имею ввиду, что реально никакой: даже не будет вываливания ошибки в консоль. Наш код просто молча не будет работать.
Поэтому существует следующее правило: ваш
код должен ловить только те исключения, с
которыми знает, как справится. Если исключение
не известное, то его нужно пробросить
дальше с помощью throw
. В этом случае
его выше поймает кто-то более осведомленный
либо исключение вывалится ошибкой в консоль.
Давайте поправим наш код:
catch (error) {
if (error.name == 'SyntaxError') {
alert('Некорректный JSON продукта');
} else if (error.name == 'ProductCostError') {
alert('У продукта отсутствует цена или количество');
} else {
throw error; // пробрасываем исключение далее
}
}
Дан следующий код:
try {
let arr = JSON.parse(json);
for (let i = 0; i < arr.length; i++) {
localStorage.setItem(i, arr[i]);
}
} catch (error) {
if (error.name == 'QuotaExceededError') {
alert('закончилось место в хранилище');
}
if (error.name == 'SyntaxError') {
alert('некорректный JSON');
}
}
Что не так с этим кодом? Исправьте его на более удачный.