Содержание
По мере накопления опыта программирования ваши приложения становятся все более надежными. Большую роль в этом играет совершенствование методов кодирования, но вы также научитесь учитывать крайние случаи. Если что-то может пойти не так, оно пойдет не так: обычно это происходит, когда первый пользователь получает доступ к вашей новой системе.
Некоторых ошибок можно избежать:
JavaScript-линтер или хороший редактор может выявить синтаксические ошибки, такие как неправильное написание утверждений и отсутствие скобок.
С помощью проверки можно выявить ошибки в данных, полученных от пользователей или других систем. Никогда не делайте предположений относительно изобретательности пользователя, чтобы вызвать хаос. Запрашивая возраст человека, вы можете ожидать целое число, но он может оставить поле пустым, ввести отрицательное значение, использовать дробное значение или даже полностью набрать ”двадцать один” на своем родном языке.
Помните, что валидация на стороне сервера очень важна. Клиентская проверка на стороне браузера может улучшить пользовательский интерфейс, но пользователь может использовать приложение, в котором JavaScript отключен, не загружается или не выполняется. Возможно, ваш API вызывается не в браузере!
Другие ошибки во время выполнения невозможно избежать:
Сеть может выйти из строя занятый сервер или приложение может слишком долго отвечать на запросы скрипт или другой актив может выйти из строя приложение может выйти из строя диск или база данных может выйти из строя серверная ОС может выйти из строя инфраструктура узла может выйти из строя
Они могут быть временными. Вы не можете в обязательном порядке закодировать свой способ решения этих проблем, но вы можете предвидеть проблемы, принять соответствующие меры и сделать свое приложение более устойчивым.
Показывать ошибку - последнее средство
Все мы сталкивались с ошибками в приложениях. Некоторые из них полезны:
”Этот файл уже существует. Не хотите ли вы перезаписать его?”.
Другие - не очень:
“ERROR 5969”
Показ ошибки пользователю должен быть последним средством после исчерпания всех других вариантов.
Возможно, вы сможете игнорировать некоторые менее важные ошибки, например, ошибку загрузки изображения. В других случаях возможны действия по исправлению или восстановлению. Например, если вы не можете сохранить данные на сервере из-за сбоя в сети, вы можете временно сохранить их в IndexedDB или localStorage и повторить попытку через несколько минут. Показывать предупреждение нужно только в том случае, если повторные сохранения не удаются и пользователь рискует потерять данные. Даже в этом случае убедитесь, что пользователь может предпринять соответствующие действия. Они могут переподключиться к сети, но это не поможет, если ваш сервер не работает.
Обработка ошибок в JavaScript
Обработка ошибок в JavaScript проста, но она часто окутана тайной и может стать сложной, если рассматривать асинхронный код.
Ошибка” - это объект, который вы можете бросить, чтобы вызвать исключение - которое может остановить работу программы, если его не перехватить и не решить соответствующим образом. Вы можете создать объект Error, передав в конструктор необязательное сообщение:
const e = new Error('Произошла ошибка');
Вы также можете использовать Error как функцию без new - она по-прежнему возвращает объект Error, идентичный приведенному выше:
const e = Error('Произошла ошибка');
В качестве второго и третьего параметров можно передать имя файла и номер строки:
const e = new Error('Произошла ошибка', 'script.js', 99);
Это редко бывает необходимо, так как по умолчанию они указывают на строку, где вы создали объект Error в текущем файле.
После создания объект Error имеет следующие свойства, которые вы можете читать и записывать:
.name: имя типа Ошибка (в данном случае "Ошибка") .message: сообщение об ошибке
Следующие свойства чтения/записи также поддерживаются в Firefox:
.fileName: файл, в котором произошла ошибка .lineNumber: номер строки, в которой произошла ошибка .columnNumber: номер столбца в строке, в которой произошла ошибка .stack: трассировка стека - список вызовов функций, выполненных для достижения ошибки.
Типы ошибок
Наряду с общим Error, JavaScript поддерживает специфические типы ошибок:
Интерпретатор JavaScript будет выдавать соответствующие ошибки по мере необходимости. В большинстве случаев вы будете использовать Error или, возможно, TypeError в своем собственном коде.
Выбрасывание исключения
Создание объекта Error само по себе ничего не дает. Вы должны использовать оператор throw, чтобы ”бросить” Error и вызвать исключение:
throw new Error('Произошла ошибка');
Эта функция sum() выбрасывает ошибку TypeError, если один из аргументов не является числом - возврат никогда не выполняется:
function sum(a, b) { if (isNaN(a) || isNaN(b)) { throw new TypeError('Значение не является числом.'); } return a + b; }.
Практично throw объект Error, но вы можете использовать любое значение или объект:
throw 'Строка ошибки'; throw 42; throw true; throw { message: 'Ошибка', name: 'CustomError' };
Когда вы ”бросаете” исключение, оно поднимается вверх по стеку вызовов - если только оно не поймано. Не пойманные исключения в конце концов достигают вершины стека, где программа останавливается и выдает ошибку в консоли 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 { // это всегда выполняется console.log( 'программа завершилась' ); }.
Блок try требует либо блока catch, либо блока finally, либо обоих блоков.
Обратите внимание, что когда блок finally содержит return, это значение становится возвращаемым значением для всего блока try ... catch ... finally, независимо от любых заявлений return в блоках try и catch.
Вложенные блоки try ... catch и ошибки перебрасывания
Исключение поднимается вверх по стеку и перехватывается только один раз ближайшим блоком catch, например
try { try { console.log( sum(1, 'a') ); } catch (err) { console.error('Эта ошибка сработает', err.message); } } catch (err) { console.error('Эта ошибка не сработает', err.message); }
Любой блок catch может бросить новое исключение, которое будет поймано внешним catch. Вы можете передать первый объект Error в новый Error в свойстве cause объекта, переданного в конструктор. Это позволяет вызывать и исследовать цепочку ошибок.
В этом примере выполняются оба блока catch, потому что первая ошибка вызывает вторую:
try { try { console.log( sum(1, 'a') ); } catch (err) { console.error('Первая ошибка поймана', err.message); throw new Error('Вторая ошибка', { cause: err }); } } catch (err) { console.error('Вторая ошибка поймана', err.message); console.error('Причина ошибки:', err.cause.message); }
Выбрасывание исключений в асинхронных функциях
Вы не можете ловить исключение, выброшенное асинхронной функцией, потому что оно возникает после завершения выполнения блока 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://localhost:8888/:14
Если вы используете обратный вызов, то, согласно принятой во фреймворках и средах исполнения, таких как Node.js, в качестве первого параметра функции возвращается ошибка. Это не приводит к ”выбрасыванию” исключения, хотя при необходимости вы можете сделать это вручную:
function wait(delay = 1000, callback) { setTimeout(() => { callback('I am caught!'); }, delay); } wait(1000, (err) => { if (err) { throw new Error(err); } });
В современном ES6 при определении асинхронных функций часто лучше возвращать Promise. При возникновении ошибки метод reject Promise может вернуть новый объект 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 очень просто. А вот правильно реагировать и создавать устойчивые приложения несколько сложнее! Лучший совет: ожидайте неожиданностей и устраняйте ошибки как можно быстрее.