Разберитесь со всеми различными способами обработки ошибок в JavaScript.
Ваши приложения становятся более надежными по мере того, как вы приобретаете опыт программирования. Большую роль играют улучшения в методах кодирования, но вы также научитесь учитывать крайние случаи. Если что-то может пойти не так, так оно и будет: обычно, когда первый пользователь получает доступ к вашей новой системе.
Некоторых ошибок можно избежать:
- Линтер JavaScript или хороший редактор могут обнаруживать синтаксические ошибки, такие как операторы с ошибками и отсутствующие скобки.
- Вы можете использовать проверку для обнаружения ошибок в данных от пользователей или других систем. Никогда не делайте предположений об изобретательности пользователя, чтобы вызвать хаос. Вы можете ожидать целое число, когда спрашиваете чей-то возраст, но может ли он оставить поле пустым, ввести отрицательное значение, использовать дробное значение или даже полностью ввести «двадцать один» на своем родном языке.
- Помните, что проверка на стороне сервера необходима. Проверка на стороне клиента на основе браузера может улучшить пользовательский интерфейс, но пользователь может использовать приложение, в котором JavaScript отключен, не загружается или не выполняется. Возможно, ваш API вызывает не браузер!
Других ошибок во время выполнения избежать невозможно:
- сеть может выйти из строя
- занятому серверу или приложению может потребоваться слишком много времени для ответа
- срок действия скрипта или другого актива может истечь
- приложение может не работать
- диск или база данных могут выйти из строя
- серверная ОС может выйти из строя
- инфраструктура хоста может выйти из строя
Они могут быть временными. Вы не можете обязательно закодировать свой способ решения этих проблем, но вы можете предвидеть проблемы, предпринять соответствующие действия и сделать ваше приложение более устойчивым.
Отображение ошибки — крайняя мера
Мы все сталкивались с ошибками в приложениях. Некоторые из них полезны:
«Файл уже существует. Хотите перезаписать его?»
Другие меньше:
"ОШИБКА 5969"
Отображение ошибки пользователю должно быть последним средством после исчерпания всех других вариантов.
Вы можете игнорировать некоторые менее важные ошибки, такие как сбой загрузки изображения. В других случаях возможны корректирующие или восстановительные действия. Например, если вы не можете сохранить данные на сервер из-за сбоя в сети, вы можете временно сохранить их в IndexedDB или localStorage и повторить попытку через несколько минут. Предупреждение должно быть необходимо только в том случае, когда повторные сохранения завершаются неудачно и пользователь рискует потерять данные. Даже в этом случае: убедитесь, что пользователь может предпринять соответствующие действия. Они могут повторно подключиться к сети, но это не поможет, если ваш сервер не работает.
Обработка ошибок в JavaScript
Обработка ошибок в JavaScript проста, но часто окутана тайной и может усложниться при рассмотрении асинхронного кода.
Ошибка — это объект, который вы можете сгенерировать, чтобы вызвать исключение, которое может остановить программу, если оно не будет перехвачено и обработано должным образом. Вы можете создать объект Error
, передав конструктору необязательное сообщение:
const e = new Error(‘An error has occurred’);
Вы также можете использовать Error
как функцию без new
— она по-прежнему возвращает объект Error
, идентичный приведенному выше:
const e = Error('An error has occurred');
Вы можете передать имя файла и номер строки в качестве второго и третьего параметров:
const e = new Error('An error has occurred', 'script.js', 99);
В этом редко возникает необходимость, так как по умолчанию используется строка, в которой вы создали объект Error
в текущем файле.
После создания объект Error
имеет следующие свойства, которые вы можете читать и записывать:
.name
: имя типаError
(в данном случае"Error"
).message
: сообщение об ошибке
Следующие свойства чтения/записи также поддерживаются в Firefox:
.fileName
: файл, в котором произошла ошибка.lineNumber
: номер строки, в которой произошла ошибка.columnNumber
: номер столбца в строке, где произошла ошибка.stack
: трассировка стека - список вызовов функций для достижения ошибки.
Типы ошибок
Помимо общего Error
, JavaScript поддерживает определенные типы ошибок:
EvalError
: вызваноeval()
RangeError
: значение вне допустимого диапазонаReferenceError
: возникает при разыменовании недопустимой ссылкиSyntaxError
: неверный синтаксисTypeError
: значение недопустимого типаURIError
: неверные параметры переданы вencodeURI()
илиdecodeURI()
AggregateError
: несколько ошибок, заключенных в одну ошибку, которая обычно возникает при вызове операции, такой какPromise.all()
.
При необходимости интерпретатор JavaScript вызовет соответствующие ошибки. В большинстве случаев вы будете использовать Error
или, возможно, TypeError
в своем собственном коде.
Создание исключения
Создание объекта Error
само по себе ничего не делает. Вы должны Error
вызвать исключение:
throw new Error('An error has occurred');
Эта функция sum()
выдает TypeError
, когда любой из аргументов не является числом — return
никогда не выполняется:
function sum(a, b) { if (isNaN(a) || isNaN(b)) { throw new TypeError('Value is not a number.'); } return a + b; }
Практично использовать throw
объект Error
, но вы можете использовать любое значение или объект:
throw 'Error string'; throw 42; throw true; throw { message: 'An error', name: 'CustomError' };
Когда вы throw
создаете исключение, оно всплывает в стеке вызовов, если оно не перехвачено. Неперехваченные исключения в конечном итоге достигают вершины стека, где программа останавливается и показывает ошибку в консоли DevTools, например.
Uncaught TypeError: Value is not a number. sum https://mysite.com/js/index.js:3
Перехват исключений
Вы можете ловить исключения в блоке try ... catch
:
try { console.log( sum(1, 'a') ); } catch (err) { console.error( err.message ); }
При этом выполняется код в блоке try {}
, но при возникновении исключения блок catch {}
получает объект, возвращенный блоком throw
.
Блок catch
может анализировать ошибку и реагировать соответствующим образом, например.
try { console.log( sum(1, 'a') ); } catch (err) { if (err instanceof TypeError) { console.error( 'wrong type' ); } else { console.error( err.message ); } }
Вы можете определить необязательный блок finally {}
, если вам требуется код для запуска независимо от того, выполняется ли код try
или catch
. Это может быть полезно при очистке, например. чтобы закрыть соединение с базой данных в Node.js или Deno:
try { console.log( sum(1, 'a') ); } catch (err) { console.error( err.message ); } finally { // this always runs console.log( 'program has ended' ); }
Для блока try
требуется либо блок catch
, либо блок finally
, либо оба.
Обратите внимание, что когда блок finally
содержит return
, это значение становится возвращаемым значением для всего try ... catch ... finally
независимо от каких-либо операторов return
в блоках try
и catch
.
Повтор сеанса с открытым исходным кодом
Отладка веб-приложения в рабочей среде может быть сложной и трудоемкой. OpenReplay — это альтернатива FullStory, LogRocket и Hotjar с открытым исходным кодом. Это позволяет вам отслеживать и воспроизводить все, что делают ваши пользователи, и показывает, как ваше приложение ведет себя при каждой проблеме. Это похоже на то, как если бы инспектор вашего браузера был открыт, когда вы смотрите через плечо вашего пользователя. OpenReplay — единственная доступная в настоящее время альтернатива с открытым исходным кодом.
Удачной отладки, для современных фронтенд-команд — Начните бесплатно отслеживать свое веб-приложение.
Вложенныеtry … catchБлоки и повторная выдача ошибок
Исключение всплывает вверх по стеку и перехватывается один раз только ближайшим блоком catch
, например
try { try { console.log( sum(1, 'a') ); } catch (err) { console.error('This error will trigger', err.message); } } catch (err) { console.error('This error will not trigger', err.message); }
Любой catch
блок может throw
создать новое исключение, которое перехватывается внешним catch
. Вы можете передать первый объект Error
новому Error
в свойстве cause
объекта, переданного конструктору. Это дает возможность поднять и изучить цепочку ошибок.
В этом примере выполняются оба блока catch
, потому что первая ошибка вызывает вторую:
try { try { console.log( sum(1, 'a') ); } catch (err) { console.error('First error caught', err.message); throw new Error('Second error', { cause: err }); } } catch (err) { console.error('Second error caught', err.message); console.error('Error cause:', err.cause.message); }
Генерация исключений в асинхронных функциях
Вы не можете catch
создать исключение, вызванное асинхронной функцией, потому что оно вызывается после завершения выполнения блока try ... catch
. Это не удастся:
function wait(delay = 1000) { setTimeout(() => { throw new Error('I am never caught!'); }, delay); } try { wait(); } catch(err) { // this will never run console.error('caught!', err.message); }
По истечении одной секунды на консоли отображается:
Uncaught Error: I am never caught! wait http://server.com/script.js:3
Если вы используете обратный вызов, соглашение, принятое во фреймворках и средах выполнения, таких как Node.js, заключается в том, чтобы возвращать ошибку в качестве первого параметра этой функции. Это не throw
исключение, хотя при необходимости вы можете сделать это вручную:
function wait(delay = 1000, callback) { setTimeout(() => { callback('I am caught!'); }, delay); } wait(1000, (err) => { if (err) { throw new Error(err); } });
В современном ES6 часто лучше возвращать Promise при определении асинхронных функций. При возникновении ошибки метод Promise reject
может вернуть новый объект Error
(хотя возможно любое значение или объект):
function wait(delay = 1000) { return new Promise((resolve, reject) => { if (isNaN(delay) || delay < 0) { reject(new TypeError('Invalid delay')); } else { setTimeout(() => { resolve(`waited ${ delay } ms`); }, delay); } }) }
Метод Promise.catch()
выполняется при передаче недопустимого параметра delay
, поэтому он может реагировать на возвращаемый объект Error
:
// this fails - the catch block is run wait('x') .then( res => console.log( res )) .catch( err => console.error( err.message )) .finally(() => console.log('done'));
Любая функция, которая возвращает Promise
, может быть вызвана функцией async
с использованием оператора await
. Вы можете поместить это в блок try ... catch
, который работает идентично приведенному выше примеру .then
/.catch
Promise, но может быть немного проще для чтения:
// Immediately-Invoked (Asynchronous) Function Expression (async () => { try { console.log( await wait('x') ); } catch (err) { console.error( err.message ); } finally { console.log('done'); } })();
Ошибки неизбежны
В JavaScript легко создавать объекты ошибок и вызывать исключения. Правильно реагировать и создавать устойчивые приложения несколько сложнее! Лучший совет: ожидайте неожиданного и исправляйте ошибки как можно скорее.
Первоначально опубликовано на https://blog.openreplay.com 21 февраля 2022 г.