Время на прочтение
8 мин
Количество просмотров 12K
Всем привет! Вдохновленные успехом предыдущей статьи, которая была написана в преддверии запуска курса «Fullstack разработчик JavaScript«, мы решили продолжить серию статей для новичков и всех тех, кто только начинает заниматься программированием на языке JavaScript. Cегодня мы поговорим об ошибках, которые случаются в JS, а также о том, как именно с ними бороться.
Отдебажь за человека одну ошибку, и он будет благодарен тебе один пулл реквест. Научи его дебажить самостоятельно, и он будет благодарен тебе весь проект.
Неизвестный тимлид
Типичные ошибки начинающих
Итак, начнем с самых примитивных ошибок. Допустим, вы только недавно закончили изучать основы HTML и CSS и теперь активно принялись за программирование на JavaScript. Для примера: вы хотите, чтобы при клике на кнопку у вас открывалось, к примеру, скрытое до этого момента модальное окно. Так же вы хотите, чтобы у вас по нажатию на крестик это окно закрывалось. Интерактивный пример доступен здесь (я выбрал bitbucket из-за того, что его интерфейс мне кажется самым простым, да и не все же на гитхабе сидеть).
let modal_alert = document.querySelector(".modal_alert")
let hero__btn = document.querySelector(".hero__btn")
let modal_close = document.querySelector(".modal-close ")
//мы выбрали из DOM модели наши элементы. К слову, я использую bulma для упрощения процесса верстки
//теперь мы хотим провести над нашими элементами какие-то операции:
hero__btn.addEventListener("click", function(){
modal_alert.classList.add("helper_visible");
})
modal_close.addEventListener("click", function(){
modal_alert.classList.remove("helper_visible");
})
//если мы хотим увидеть форму, то просто вешаем доп. класс, в котором прописано css-свойство display:flex. И наоборот, если хотим скрыть.
В нашем index.html, кроме верстки, мы внутри тэга head вставляем наш script:
<script src="code.js"></script>
В index.html кроме верстки внутри тэга head мы вставляем наш script:
<script src="code.js"></script>
Однако, несмотря на то, что мы все подключили, ничего не заработает и вылетит ошибка:
Что весьма печально, новички часто теряются и не понимают, что делать с красными строчками, словно это приговор какой-то, а не подсказка о том, что не так в вашей программе. Если перевести, то браузер говорит нам, что он не может прочитать свойство addEventListener нулевого значения. Значит, почему-то из DOM модели мы не получили наш элемент. Какой алгоритм действий нужно предпринять?
Во-первых, посмотрите в какой момент у вас вызывается javascript. Браузер читает ваш html-код сверху вниз, как вы читаете, например, книгу. Когда он увидит тэг script, то сразу исполнит его содержимое и продолжит чтение следующих элементов, не особо заботясь о том, что в своем скрипте вы пытаетесь получить элементы DOM, а он их еще не прочитал и, следовательно, не построил модель.
Что делать в таком случае? Просто добавьте атрибут defer внутрь вашего тэга скрипт (или async, но я не буду сейчас вдаваться в подробности их работы, это можно прочитать здесь ). Или можете просто переместить вниз ваш тэг script перед закрывающим body, это тоже сработает.
Во-вторых, проверьте опечатки. Изучите методологию БЭМ — она полезна ещё и тем, что вы хорошо знаете, как пишется ваш элемент — ведь пишите классы по единой логике, и стараетесь пользоваться только правильным английским языком. Или копируете сразу название элемента в JS файл.
Отлично. Теперь, когда вы поправили ошибки, можете насладиться рабочей версией кода по следующему адресу.
Загадочная ошибка
Больше всего новичков вводит в ступор странная ошибка последней строчки кода. Приведем пример:
В консоли выводится что-то непонятное. Если переводить, то буквально это «Неожиданный конец ввода» — и что с этим делать? Кроме того, новичок по привычке смотрит на номер строки. На ней вроде все нормально. И почему тогда консоль на нее указывает?
Все просто. Что бы понимать, как интерпретировать вашу программу, интерпретатору JS нужно знать, где заканчивается тело функции, и где заканчивается тело цикла. В данном варианте кода я абсолютно намеренно забыл последнюю фигурную скобку:
// тут у нас просто два массива с заголовками и статьями
let root = document.getElementById("root"); // реактно подобно использую root
let article__btn = document.querySelector("article__btn");
// при клике на кнопку прочитаем статью
article__btn.onclick = () => {
for (let i = 0; i < headers.length; i++) {
root.insertAdjacentHTML("beforeend", `
<div class="content is-medium">
<h1>${headers[i]} </h1>
<p>${paragraps[i]}</p>
</div>`)
//изъятие фигурной скобки выполнено профессионалами. Не повторять на продакшене
}
Теперь JavaScript не понимает, где у него конец тела функции, а где конец цикла и не может интерпретировать код.
Что делать в данном случае? В любом современном редакторе кода, если вы поставите курсор перед открывающей скобкой, подсветится его закрывающий вариант (если редактор еще не начал подчеркивать эту ошибку красным цветом). Просмотрите код еще раз внимательно, держа в голове, что в JS не бывает одиноких фигурных скобок. Проблемный вариант можно посмотреть здесь, а исправленный — вот тут.
Дробим код
Чаще всего стоит заниматься написанием кода, тестируя его работу небольшими кусочками.
Или как нормальный человек изучить TDD
К примеру, вам нужно простую программу, которая принимает данные на вход от пользователя, складывает их в массив и после этого выводит их средние значения:
let input_number = prompt("Введите количество переменных");
// определяем, какое количество переменных к нам придет
let numbers = [];
function toArray(input_number){
for (let i = 0; i < input_number; i++) {
let x = prompt(`Введите значение ${i}`);
numbers.push(x); // и складываем значения в массив
}
}
toArray(input_number);
function toAverage(numbers){
let sum = 0;
for (let i = 0; i < numbers.length; i++) {
sum += numbers[i];
}
return sum/numbers.length;
}
alert(toAverage(numbers));
На первый неискушенный взгляд, в данном коде вполне все нормально. В нем есть основная логика, раздробленная на две функции, каждую из которой можно применять потом отдельно. Однако опытный программист сразу скажет, что это не заработает, ведь из prompt данные к нам приходят в виде строки. Причем JS (таков его толерантно-пофигистичный характер) нам все запустит, но на выходе выдаст настолько невероятную чепуху, что даже будет непросто понять, как мы дошли до жизни такой. Итак, давайте попробуем что-нибудь посчитать в нашем интерактивном примере. Введем допустим число 3 в количество переменных, и 1 2 3 в поле ввода данных:
Что? Чего? Ладно, это JavaScript. Поговорим лучше, как мы могли бы избежать такого странного вывода.
Надо было писать на Python, он бы по-человечески предупредил нас об ошибке
. Нам надо было после каждого подозрительного момента сделать вывод типа переменных и смотреть, в каком состоянии находится наш массив.
Вариант кода, в котором вероятность неожиданного вывода снижена:
let input_number = prompt("Введите количество переменных");
console.log(typeof(input_number));
let numbers = [];
function toArray(input_number){
for (let i = 0; i < input_number; i++) {
let x = prompt(`Введите значение ${i}`);
numbers.push(x);
}
}
toArray(input_number);
console.log(numbers);
function toAverage(numbers){
let sum = 0;
for (let i = 0; i < numbers.length; i++) {
sum += numbers[i];
}
return sum/numbers.length;
}
console.log(typeof(toAverage(numbers)));
alert(toAverage(numbers));
Иными словами, все подозрительные места, в которых что-то могло пойти не так, я вывел в консоль, чтобы убедиться, что все идет так, как я ожидаю. Конечно, данные console.log — детские игрушки и в норме, естественно, нужно изучить любую приличную библиотеку для тестирования. Например эту. Результат этой отладочной программы можно увидеть в инструментах разработчика здесь. Как починить, я думаю, вопросов не будет, но если если интересно, то вот (и да, это можно сделать просто двумя плюсами).
Шаг вперед: осваиваем Chrome Dev Tools
Дебаг с использованием console.log в 2019 — это уже несколько архаичная штука (но мы все равно ее никогда ее не забудем, она уже нам как родная). Каждый разработчик, который мечтает носить гордое звание профессионала, должен освоить богатый инструментарий современных средств разработки.
Попробуем починить проблемные места в нашем коде с помощью Dev Tools. Если нужна документация с примерами, всё можно прочитать вот здесь. А мы попробуем разобрать предыдущий пример с помощью Dev Tools.
Итак, открываем пример. У нас явно запрятался какой-то баг в коде, но как понять, в какой момент JavaScript начал что-то неправильно считать?
Правильно, оборачиваем эту радость тестами на тип переменной, это же очень просто
Идем во вкладку Sources в инструментах разработчика. Откройте файл code.js. У вас будут 3 части: первая слева, в которой отображается список файлов и вторая — в которой у нас отображается код. Но больше всего информации мы сможете почерпнуть из третьей части снизу, в которой отображается ход выполнения нашего кода. Давайте поставим breakpoint на 15 строчке (для этого надо щелкнуть по номеру строки в окне, где у нас отображается код, после чего у вас появится голубая метка). Перезапустите страницу, и введите любые значения в нашу программу.
Теперь вы можете вытащить из нижней панели debug массу полезной информации. Вы обнаружите, что JS не особенно задумываясь над типом переменных
ведь статистические языки тупо лучше и нужно писать только на них, чтобы получать предсказуемо работающие и быстрые программы
складывает переменные в виде строки в наш массив. Теперь, осознав картину происходящего, мы можем принять контрмеры.
Учимся перехватывать ошибки
Конструкция try… catch встречается во всех современных языках программирования. Зачем эта синтаксическая конструкция нужна практически? Дело в том, что при возникновении ошибки в коде, он останавливает свое выполнение на месте ошибки — и все, дальнейшие инструкции интерпретатор не исполнит. В реально работающем приложении, из нескольких сотен строчек кода, нас это не устроит. И предположим, что мы хотим перехватить код ошибки, передать разработчику ее код, и продолжить выполнение дальше.
Наша статья была бы неполной без краткого описания основных типов ошибки в JavaScript:
- Error — общий конструктор объекта ошибки.
- EvalError — тип ошибки, появляющийся во время ошибок исполнения
eval(), но не синтаксических, а при неправильном использовании этой глобальной функции. - RangeError — происходит, когда вы выходите за пределы допустимого диапазона в исполнении вашего кода.
- ReferenceError — происходит, когда вы пытаетесь вызвать переменную, функцию или объект, которых нет в программе.
- SyntaxError — ошибка в синтаксисе.
- TypeError — происходит при попытке создания объекта с неизвестным типом переменной или при попытке вызова несуществующего метода
- URIError — редко встречающий код, который возникает при неправильном использовании методов encodeURL и DecodeURL.
Здорово, давайте теперь немного попрактикуемся и посмотрим на практике, где мы можем использовать конструкцию try… catch. Сам принцип работы данной конструкции совсем простой — интерпретатор пытается исполнить код внутри try, если получается — то все продолжается, словно этой конструкции никогда не было. А вот если произошла ошибка — мы ее перехватываем и можем обработать, к примеру, сказав пользователю, где именно он допустил промах.
Давайте создадим самый простой калькулятор (даже калькулятором его называть громко, я бы сказал:«исполнитель введенных выражений»). Его интерактивный пример можно найти здесь. Хорошо, давайте теперь посмотрим на наш код:
let input = document.querySelector("#enter");
let button = document.querySelector("#enter_button");
let result_el = document.querySelector("#result ");
button.onclick = () => {
try {
let result = eval(input.value); //пробуем, если все будет корректно, тогда catch не сработает
result_el.innerHTML = result;
} catch (error) {
console.error(error.name);
result_el.innerHTML = "Вы что-то не то ввели, молодой человек<br> Подумайте еще раз";
//можно пользователю объяснять, что он не прав, если он допустил ошибку
//хотя естественно пользователю лучше не давать эту возможность))
}
}
Если вы попробуете ввести корректное математическое выражение, то все сработает нормально. Однако попробуйте ввести некорректное выражение, к примеру, просто строку, тогда программа выведет пользователю соответствующее предупреждение.
Надеюсь, вы прочитаете еще статьи, в которых объясняются другие части перехвата ошибок, такие например, как эта , чтобы расширить свое понимание в отладке программ, и изучите другие синтаксические конструкции, такие как finally, а также генерацию своих собственных ошибок.
На этом все. Надеюсь, эта статья оказалась полезна и теперь, при отладке приложений, вы будете чувствовать себя более уверенно. Мы разобрали типичные ошибки от самых элементарных, которые делают новички программирующие на JS всего несколько дней, до техники перехвата ошибок, которые применяют более продвинутые разработчики.
И по традиции, полезные ссылочки:
- Пишем собственный фреймворк для тестирования. Полезно для общего понимания стратегии тестирования.
- Полная документация по ошибкам, в том числе и экспериментальные фичи
- Невероятно полезная статья на MDN, которая описывает большинство проблем, которые возникают в начале разработки на JS: отладку, полифиллы, дебагер и многое другое
На этом все. Ждем ваши комментарии и приглашаем на бесплатный вебинар, где поговорим о возможностях фреймворка SvelteJS.
Опубликовано: среда, 29 марта 2023 г. в 09:06
- JavaScript
В этом руководстве мы погрузимся в обработку ошибок JavaScript, чтобы вы могли выбрасывать исключения, обнаруживать и обрабатывать собственные ошибки.
Опытные разработчики ожидают неожиданного. Если что-то может пойти не так, так оно и будет — обычно в тот момент, когда первый пользователь получает доступ к вашему новому приложению.
Некоторых ошибок веб-приложений можно избежать, например:
- Хороший редактор или линтер может отлавливать синтаксические ошибки.
- Хорошая валидация может выявить ошибки пользовательского ввода.
- Надёжные процессы тестирования могут обнаруживать логические ошибки.
И всё же ошибки остаются. Браузеры могут сбоить или не поддерживать API, который мы используем. Серверы могут дать сбой или слишком долго отвечать. Сетевое соединение может выйти из строя или стать ненадёжным. Проблемы могут быть временными, но мы не может программировать такие проблемы. Однако мы можем предвидеть проблемы, принимать меры по их устранению и повышать отказоустойчивость нашего приложения.
Отображение сообщения об ошибке — крайняя мера
В идеале пользователи никогда не должны видеть сообщения об ошибке.
Мы можем игнорировать незначительные проблемы, такие как невозможность загрузки декоративного изображения. Мы могли бы решить более серьёзные проблемы, такие как сбои сохранения данных Ajax локально и загружая их позже. Ошибка становиться необходимой только тогда, когда пользователь рискует потерять данные, предполагая, что он может что-то с этим сделать.
Поэтому необходимо выявлять ошибки по мере их возникновения и определять наилучшие действия. Вызов и перехват ошибок в приложении JavaScript поначалу может быть пугающим, но, возможно, это проще, чем вы ожидаете.
Как JavaScript обрабатывает ошибки
Когда оператор JavaScript приводит к ошибке, говорят, что он генерирует (выбрасывает) исключение. JavaScript создаёт и выбрасывает объект Error, описывающий ошибку. Мы можем увидеть это в действии на CodePen. Если установить в десятичные разряды (decimal places) отрицательное число, мы увидим сообщение об ошибке в консоли внизу. (Обратите внимание, что мы не встраиваем CodePen в это руководство, потому что нужно иметь возможно видеть вывод консоли, чтобы этот пример имел смысл)
Результат не обновиться, и мы увидим сообщение RangeError в консоли. Следующая функция выдаёт ошибку, когда dp имеет отрицательно значение:
// division calculation
function divide(v1, v2, dp) {
return (v1 / v2).toFixed(dp);
}
После выдачи ошибки интерпретатор JavaScript проверяет наличие кода обработки исключений. В функции Division() ничего нет, поэтому она проверяет вызывающую функцию:
// show result of division
function showResult() {
result.value = divide(
parseFloat(num1.value),
parseFloat(num2.value),
parseFloat(dp.value)
);
}
Интерпретатор повторяет процесс для каждой функции в стеке вызовов, пока не произойдёт одно из следующих событий:
- Он находит обработчик исключений.
- Он достигает верхнего уровня кода (что приводит к завершению программы и отображению ошибки в консоли, как показано в примере на CodePen выше).
Перехват исключений
Мы можем добавить обработчик исключений в функцию divide() с помощью блока try...catch:
// division calculation
function divide(v1, v2, dp) {
try {
return (v1 / v2).toFixed(dp);
}
catch(e) {
console.log(`
error name : ${ e.name }
error message: ${ e.message }
`);
return 'ERROR';
}
}
Функция выполняет код в блоке try {}, но при возникновении исключения выполняется блок catch {} и получает выброшенный объект ошибки. Как и прежде, попробуйте в decimal places установить отрицательное число в этой демонстрации CodePen.
Теперь result показывает ERROR. Консоль показывает имя ошибки и сообщение, но это выводится оператором console.log и не завершает работу программы.
Примечание: эта демонстрация блока
try...catchизлишняя для базовой функции, такой какdivide(). Как мы увидим ниже, проще убедиться, чтоdpравен нулю или больше.
Можно определить не обязательный блок finally {}, если требуется, чтобы код запускался при выполнении кода try или catch:
function divide(v1, v2, dp) {
try {
return (v1 / v2).toFixed(dp);
}
catch(e) {
return 'ERROR';
}
finally {
console.log('done');
}
}
В консоль выведется done
, независимо от того, успешно ли выполнено вычисление или возникла ошибка. Блок finally обычно выполняет действия, которые в противном случае нам пришлось бы повторять как в блоке try, так и в блоке catch. Например, отмену вызова API или закрытие соединения с базой данных.
Для блока try требуется либо блок catch, либо блок finally, либо и то и другое. Обратите внимание, что когда блок finally содержит оператор return, это значение становится возвращаемым значением для всей функции; другие операторы в блоках try или catch игнорируются.
Вложенные обработчики исключений
Что произойдёт, если мы добавим обработчик исключений к вызывающей функции showResult()?
// show result of division
function showResult() {
try {
result.value = divide(
parseFloat(num1.value),
parseFloat(num2.value),
parseFloat(dp.value)
);
}
catch(e) {
result.value = 'FAIL!';
}
}
Ответ… ничего! Блок catch никогда не выполняется, потому что в функции divide() блок catch обрабатывает ошибку.
Тем не менее мы могли бы программно генерировать новый объект Error в divide() и при желании передать исходную ошибку в свойстве cause второго аргумента:
function divide(v1, v2, dp) {
try {
return (v1 / v2).toFixed(dp);
}
catch(e) {
throw new Error('ERROR', { cause: e });
}
}
Это вызовет блок catch в вызывающей функции:
// show result of division
function showResult() {
try {
//...
}
catch(e) {
console.log( e.message ); // ERROR
console.log( e.cause.name ); // RangeError
result.value = 'FAIL!';
}
}
Стандартные типы ошибок JavaScript
Когда возникает исключение, JavaScript создаёт и выдаёт объект, описывающий ошибку, используя один из следующих типов.
SyntaxError
Ошибка, возникающая из-за синтаксически недопустимого кода, такого как отсутствующая скобка:
if condition) { // SyntaxError
console.log('condition is true');
}
Примечание: такие языки, как C++ и Java, сообщают об ошибках синтаксиса во время компиляции. JavaScript — интерпретируемый язык, поэтому синтаксические ошибки не выявляются до тех пор, пока код не запустится. Любой хороший редактор кода или линтер могут обнаружить синтаксические ошибки до того, как мы попытаемся запустить код.
ReferenceError
Ошибка при доступе к несуществующей переменной:
function inc() {
value++; // ReferenceError
}
Опять, хороший редактор кода или линтер могут обнаружить эту проблему.
TypeError
Ошибка возникает, когда значение не соответствует ожидаемому типу, например, при вызове несуществующего метода объекта:
const obj = {};
obj.missingMethod(); // TypeError
RangeError
Ошибка возникает, когда значение не входит в набор или диапазон допустимых значений. Используемый выше метод toFixed() генерирует эту ошибку, потому что он ожидает значение от 0 до 100:
const n = 123.456;
console.log( n.toFixed(-1) ); // RangeError
URIError
Ошибка выдаваемая функциями обработки URI, такими как encodeURI() и decodeURI(), при обнаружении неправильных URI:
const u = decodeURIComponent('%'); // URIError
EvalError
Ошибка возникающая при передаче строки, содержащей не валидный JavaScript код, в функцию eval():
eval('console.logg x;'); // EvalError
Примечание: пожалуйста, не используйте
eval()! Выполнение произвольного кода, содержащегося в строке, возможно, созданной на основе пользовательского ввода, слишком опасно!
AggregateError
Ошибка возникает, когда несколько ошибок объединены в одну ошибку. Обычно возникает при вызове такой операции, как Promise.all(), которая возвращает результаты нескольких промисов.
InternalError
Нестандартная ошибка (только в Firefox) возникает при возникновении внутренней ошибки движка JavaScript. Обычно это результат того, что что-то занимает слишком много памяти, например, большой массив или слишком много рекурсии
.
Error
Наконец, есть общий объект Error, чаще всего используемый при реализации собственных исключений… о котором мы поговорим дальше.
Генерация/выбрасывание собственных исключений
Мы можем использовать throw для генерации/выбрасывания собственных исключений, когда возникает ошибка — или должна произойти. Например:
- нашей функции не передаются валидные параметры
- ajax-запрос не возвращает ожидаемые данные
- обновление DOM завершается ошибкой, поскольку узел не существует
Оператор throw фактически принимает любое значение или объект. Например:
throw 'A simple error string';
throw 42;
throw true;
throw { message: 'An error', name: 'MyError' };
Исключения генерируются для каждой функции в стеке вызовов до тех пор, пока они не будут перехвачены обработчиком исключений (catch). Однако на практике мы хотим создать и сгенерировать объект Error, чтобы он действовал идентично стандартным ошибкам, выдаваемым JavaScript.
Можно создать общий объект Error, передав необязательное сообщение конструктору:
throw new Error('An error has occurred');
Так же Error можно использовать как функцию, без new. Она возвращает объект Error, идентичный приведённому выше:
throw Error('An error has occurred');
При желании можно передать имя файла и номер строки в качестве второго и третьего параметров:
throw new Error('An error has occurred', 'script.js', 99);
В этом редко возникает необходимость, так как по умолчанию они относятся к файлу и строке, где мы вызвали объект Error. (Также их сложно поддерживать, поскольку наши файлы меняются!)
Мы можем определить общие объекты Error, но по возможности следует использовать стандартный тип Error. Например:
throw new RangeError('Decimal places must be 0 or greater');
Все объекты Error имеют следующие свойства, которые можно проверить в блоке catch:
.name: имя типа ошибки, напримерErrorилиRangeError..message: сообщение об ошибке.
В Firefox поддерживаются следующие нестандартные свойства:
.fileName: файл, в котором произошла ошибка..lineNumber: номер строки, в которой произошла ошибка..columnNumber: номер столбца, в котором произошла ошибка..stack: трассировка стека со списком вызовов функций, сделанных до возникновения ошибки.
Мы можем изменить функцию divide() так, чтобы она вызывала ошибку RangeError, когда количество знаков после запятой не является числом, меньше нуля и больше восьми:
// division calculation
function divide(v1, v2, dp) {
if (isNaN(dp) || dp < 0 || dp > 8) {
throw new RangeError('Decimal places must be between 0 and 8');
}
return (v1 / v2).toFixed(dp);
}
Точно так же мы могли бы выдать Error или TypeError, когда значения делимого не является числом, чтобы предотвратить результат NaN:
if (isNaN(v1)) {
throw new TypeError('Dividend must be a number');
}
Также можно обрабатывать делитель, который не является числом или равен нулю. JavaScript возвращает Infinity при делении на ноль, но это может запутать пользователя. Вместо того чтобы вызывать общую ошибку, мы могли бы создать собственный тип ошибки DivByZeroError:
// new DivByZeroError Error type
class DivByZeroError extends Error {
constructor(message) {
super(message);
this.name = 'DivByZeroError';
}
}
Затем вызывать/выбрасывать его подобным образом:
if (isNaN(v2) || !v2) {
throw new DivByZeroError('Divisor must be a non-zero number');
}
Теперь добавьте блок try...catch к вызывающей функции showResult(). Он сможет получить тип любой ошибки и отреагировать соответствующим образом — в данном случае, выводя сообщение об ошибке:
// show result of division
function showResult() {
try {
result.value = divide(
parseFloat(num1.value),
parseFloat(num2.value),
parseFloat(dp.value)
);
errmsg.textContent = '';
}
catch (e) {
result.value = 'ERROR';
errmsg.textContent = e.message;
console.log( e.name );
}
}
Попробуйте ввести недопустимые нечисловые, нулевые и отрицательные значения в демонстрации на CodePen.
Окончательная версия функции divide() проверяет все входящие значения и при необходимости выдаёт соответствующую ошибку:
// division calculation
function divide(v1, v2, dp) {
if (isNaN(v1)) {
throw new TypeError('Dividend must be a number');
}
if (isNaN(v2) || !v2) {
throw new DivByZeroError('Divisor must be a non-zero number');
}
if (isNaN(dp) || dp < 0 || dp > 8) {
throw new RangeError('Decimal places must be between 0 and 8');
}
return (v1 / v2).toFixed(dp);
}
Больше нет необходимости размещать блок try...catch вокруг финального return, так как он никогда не должен генерировать ошибку. Если бы это произошло, JavaScript сгенерировал бы свою собственную ошибку и обработал бы её блоком catch в showResult()/
Ошибки асинхронной функции
Мы не можем перехватывать исключения, генерируемые асинхронными функциями на основе обратного вызова, потому что после завершения выполнения блока try...catch выдаётся ошибка. Этот код выглядит правильно, но блок catch никогда не выполнится, и через секунду консоль отобразит сообщение Uncaught Error:
function asyncError(delay = 1000) {
setTimeout(() => {
throw new Error('I am never caught!');
}, delay);
}
try {
asyncError();
}
catch(e) {
console.error('This will never run');
}
Соглашение, принятое в большинстве фреймворков и серверных сред выполнения, таких как Node.js, заключается в том, чтобы возвращать ошибку в качестве первого параметра функции обратного вызова. Это не приведёт к возникновению исключения, хотя при необходимости мы можем вручную сгенерировать ошибку:
function asyncError(delay = 1000, callback) {
setTimeout(() => {
callback('This is an error message');
}, delay);
}
asyncError(1000, e => {
if (e) {
throw new Error(`error: ${ e }`);
}
});
Ошибки на основе промисов
Обратные вызовы могут стать громоздкими, поэтому при написании асинхронного кода предпочтительнее использовать промисы. При возникновении ошибки метод 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);
}
})
}
Примечание: функции должны быть либо 100% синхронными, либо 100% асинхронными. Вот почему необходимо проверять значение
delayвнутри возвращаемого промиса. Если бы мы проверили значениеdelayи выдали ошибку перед возвратом промиса, функция стала бы синхронной при возникновении ошибки.
Метод Promise.catch() выполняется при передаче недопустимого параметра delay и получает возвращённый объект Error:
// invalid delay value passed
wait('INVALID')
.then( res => console.log( res ))
.catch( e => console.error( e.message ) )
.finally( () => console.log('complete') );
Я считаю цепочки промисов немного сложными для чтения. К счастью, мы можем использовать await для вызова любой функции, возвращающей промис. Это должно происходить внутри асинхронной функции, но мы можем перехватывать ошибки с помощью стандартного блока try...catch.
Следующая (вызываемая немедленно) асинхронная функция функционально идентична цепочке промисов выше:
(async () => {
try {
console.log( await wait('INVALID') );
}
catch (e) {
console.error( e.message );
}
finally {
console.log('complete');
}
})();
Исключительная обработка исключения
Выбрасывать объекты Error и обрабатывать исключения в JavaScript легко:
try {
throw new Error('I am an error!');
}
catch (e) {
console.log(`error ${ e.message }`)
}
Создание отказоустойчивого приложения, адекватно реагирующего на ошибки и облегчающее жизнь пользователя, является сложным испытанием. Всегда ожидайте неожиданного.
Дополнительная информация:
- MDN Порядок выполнения и обработка ошибок
- MDN try…catch
- MDN Error
Arnab Roy Chowdhury
Posted On: July 27, 2018
21612 Views
5 Min Read
JavaScript is criticized as a language that is quite difficult to debug. It doesn’t matter how perfect the code of a front-end application is, some of its functionality will get impacted especially when we get down to test it’s compatbility across different browsers. The errors occur mostly because many times developers use modern Web API or ECMA 6 scripts in their codes that are not yet browser compatible even in some most popular browser versions. In this article, we will look at the errors commonly faced by developers in their front-end application and how to minimize or get rid of them.
Uncaught TypeError
If you are debugging your JavaScript code in Google Chrome, you may have seen this error for several times. This occurs when you call a method or read a property on an undefined object.
Although this error can occur due to many reasons, a common one is the state of an element not properly initialized when UI components are rendered. Not only in JavaScript, this issue can also occur in an application developed using React JS or Angular jS. Let’s see how to fix it.
Fix – The state should be initialized using a reasonable default value in constructors.
Loading and Runtime Errors
Just like Java and C++ has compile errors, JavaScript has loading errors. Developers will not be able to detect a prominent error until it is loaded in the browser. Once loaded, it can be spotted easily when an error message is displayed showing syntax error. Runtime errors occur when the interpreter comes along some code that it cannot understand. Both loading and syntax errors are simple but can result in unexpected halting of script.
Fix – Once these errors occur, debugger in the browser can be used to find out the error in code. Misspelt syntax, a semicolon that is missed while typing, generally causes this error.
Null Object Error in Safari
This is a major cross browser compatibility issue typically associated with Safari Browsers. Often in Safari, an error occurs, and a message is displayed in the console that null is not an object. This occurs when a method is called on a null object. One can test this easily in the developer console of Safari. If you don’t have a mac system handy, you can try it out at here LambdaTest. We have more than 3000 combinations of browsers and operating systems, including Mac and Safari browsers, where you can test your website for browser compatibility.
Null and undefined are not same in JavaScript. Null means that the object has a blank value. Undefined means a variable that is not assigned. Using a strict equality operator can verify that they are not equal.
This error also occurs when the developer tries to use a DOM element before it is loaded.
Fix – An event listener is the perfect solution for this type of errors. It notifies the developer once the page is ready. The init() method can use the DOM elements once the event listener is fired.
Parse Errors
Often when hard coded values are used in JavaScript, during compilation, the code throws parse error. This occurs mostly when hard line breaks exist in the code. The compiler interprets line breaks as line ending semi-colons.
Fix– Always use parenthesis and semi-colons to make your code easier to read and avoid breaking lines.
(unknown): Script error
When the domain boundary is crossed by an uncaught JavaScript error, it violates the cross-origin policy and results in Script error. Uncaught errors are those which are not caught inside try-catch and bubble up on the window.onerror handler. A common example is, if your JavaScript code is hosted in CDN, any uncaught error gets reported as Script error. This is a security protocol built in browsers to prevent passing of data across domains which is not permitted otherwise.
Fix – Try-catch should always be used to handle errors
TypeError – Property Not Supported by Object
This error usually occurs in Internet Explorer when an undefined method is called. It can be compared to the undefined function error that occur in Chrome. For web applications that use JavaScript namespacing, this error is quite common. IE cannot bind “this” keyword to the current namespace. For example, this.isAwesome() works properly in all browsers but throws an exception in Chrome.
Fix – When using namespacing, this error can be avoided by using the actual namespace as a prefix
TypeError – Cannot Read Length
This error occurs mostly in Chrome due to an undefined variable’s length property. Normally an array has its length defined. But when the variable name of an array remains hidden or if the array is not initialized, this error happens.
Fix – This error can be fixed in 2 ways
- In the statement where the function is declared, parameters should be removed.
- The function should be invoked in the array that is declared.
Most of the errors that occur when the browser compiles the JavaScript are either undefined or null type errors. If the developer uses the strict compiler in a static checking system like Typescript, these errors can be avoided. It will give the warning that a type is not defined but expected. Even if Typescript is not used, guard clauses can be used to check if the objects are undefined before they are used.
Arnab Roy Chowdhury
Arnab Roy Chowdhury is a UI developer by profession and a blogging enthusiast. He has been writing content for about 5 years and has strong expertise in technical blogs, travelogues, and content in the latest programming languages.
Arnab Roy Chowdhury
Arnab Roy Chowdhury is a UI developer by profession and a blogging enthusiast. He has been writing content for about 5 years and has strong expertise in technical blogs, travelogues, and content in the latest programming languages.
Author’s Profile
Arnab Roy Chowdhury
Arnab Roy Chowdhury is a UI developer by profession and a blogging enthusiast. He has been writing content for about 5 years and has strong expertise in technical blogs, travelogues, and content in the latest programming languages.
Got Questions? Drop them on LambdaTest Community. Visit now

























