Перейти к содержанию

Обработка исключений

В JavaScript есть класс Error, который можно использовать для исключений. Вы выбрасываете ошибку с ключевым словом throw. Вы можете отловить её с помощью блоков try / catch, например:

1
2
3
4
5
try {
    throw new Error('Случилось что-то плохое');
} catch (e) {
    console.log(e);
}

Подтипы ошибок

Помимо встроенного класса Error, существует несколько дополнительных встроенных классов ошибок, которые наследуются от Error, которые может генерировать среда выполнения JavaScript:

RangeError

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

1
2
3
// Вызов консоли с слишком большим количеством параметров
console.log.apply(console, new Array(1000000000));
// RangeError: Невалидная длина массива

ReferenceError

Создается экземпляр ошибки, которая возникает при разыменовании недействительной ссылки. Например:

1
2
'use strict';
console.log(notValidVar); // ReferenceError: notValidVar не определена

SyntaxError

Создается экземпляр ошибки, возникающей при синтаксическом анализе кода, который не является допустимым в JavaScript.

1
1***3; // SyntaxError: Непредвиденный токен *

TypeError

Создается экземпляр ошибки, которая возникает, когда переменная или параметр имеет недопустимый тип.

1
'1.2'.toPrecision(1); // TypeError: '1.2'.toPrecision не является функцией

URIError

Создается экземпляр ошибки, которая возникает, когда в encodeURI() или decodeURI() передаются недопустимые параметры.

1
decodeURI('%'); // URIError: URI неправильно сформирован

Всегда используйте Error

Начинающие разработчики JavaScript иногда просто бросают необработанные строки, например.

1
2
3
4
5
try {
    throw 'Случилось что-то плохое';
} catch (e) {
    console.log(e);
}

Не делайте так. Основное преимущество объектов Error состоит в том, что автоматически отслеживается где они были созданы и произошли с помощью свойства stack.

Необработанные строки приводят к очень болезненной отладке и затрудняют анализ ошибок из логов.

Вам не нужно выбрасывать ошибку

Это нормально передавать объект Error. Это общепринятый код в Node.js колбэк стиле, который принимает колбэк первым параметром как объект ошибки.

1
2
3
4
5
6
7
8
9
function myFunction (callback: (e?: Error)) {
  doSomethingAsync(function () {
    if (somethingWrong) {
      callback(new Error('Это моя ошибка'))
    } else {
      callback();
    }
  });
}

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

Исключения должны быть исключительными - это частая поговорка в компьютерных науках. Это одинаково справедливо и для JavaScript (и для TypeScript) по нескольким причинам.

Неясно откуда брошено исключение

Рассмотрим следующий фрагмент кода:

1
2
3
4
5
6
try {
    const foo = runTask1();
    const bar = runTask2();
} catch (e) {
    console.log('Ошибка:', e);
}

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

Делает поэтапную обработку сложной

Вы можете попытаться сделать обработку поэтапной с помощью явного отлова вокруг каждого места, которое может бросить ошибку:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
try {
    const foo = runTask1();
} catch (e) {
    console.log('Ошибка:', e);
}
try {
    const bar = runTask2();
} catch (e) {
    console.log('Ошибка:', e);
}

Но теперь, если вам нужно передать что-то из первой задачи во вторую, код становится грязным: (обратите внимание на мутацию foo, требующую let + явную необходимость описывать ее, потому что это не может быть логически выведено от возврата runTask1):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
let foo: number; // Обратите внимание на использование `let`
// и явное описание типа
try {
    foo = runTask1();
} catch (e) {
    console.log('Ошибка:', e);
}
try {
    const bar = runTask2(foo);
} catch (e) {
    console.log('Ошибка:', e);
}

Не очень хорошо отражено в системе типов

Рассмотрим функцию:

1
2
3
4
function validate(value: number) {
    if (value < 0 || value > 100)
        throw new Error('Невалидное значение');
}

Использование Error для таких случаев - плохая идея, так как ошибка не отражена в определении типа для проверки функции (value:number) => void. Вместо этого лучший способ создать метод проверки:

1
2
3
4
function validate(value: number): { error?: string } {
    if (value < 0 || value > 100)
        return { error: 'Невалидное значение' };
}

И теперь это отражено в системе типов.

Если вы не хотите обрабатывать ошибку очень общим (простым / универсальным и т.д.) способом, не бросайте ошибку.

Комментарии