Материал, перевод которого мы сегодня публикуем, посвящён обработке JS-ошибок с помощью window.onerror. Это — особое событие браузера, которое вызывается при появлении неперехваченных ошибок. Здесь мы поговорим о том, как перехватывать ошибки с помощью обработчика события onerror, и о том, как отправлять сведения о них на сервер разработчика веб-сайта. Этот обработчик можно использовать в качестве основы собственной системы сбора и анализа информации об ошибках. Кроме того, он является одним из важнейших механизмов, применяемых в библиотеках, ориентированных на работу с ошибками, таких, как raven-js.
Прослушивание события window.onerror
Прослушивать событие onerror можно, назначив window.onerror функцию, играющую роль обработчика ошибок:
window.onerror = function(msg, url, lineNo, columnNo, error) {
// ... обработка ошибки ...
return false;
}
Эта функция вызывается при возникновении ошибки, ей передаются следующие аргументы:
msg— сообщение ошибки. Например —Uncaught ReferenceError: foo is not defined.url— адрес скрипта или документа, в котором произошла ошибка. Например —/dist/app.js.lineNo— номер строки, в которой произошла ошибка (если поддерживается).columnNo— номер столбца строки (если поддерживается).error— объект ошибки (если поддерживается).
Первые четыре аргумента сообщают разработчику о том, в каком скрипте, в какой строке и в каком столбце этой строки произошла ошибка. Последний аргумент, объект типа Error, возможно, является самым важным из всех аргументов. Поговорим об этом.
Объект Error и свойство Error.prototype.stack
На первый взгляд в объекте Error нет ничего особенного. Он содержит три вполне стандартных свойства — message, fileName и lineNumber. Эти данные, учитывая сведения, передаваемые в обработчик события window.onerror, можно считать избыточными.
Настоящую ценность в данном случае представляет собой нестандартное свойство Error.prototype.stack. Это свойство даёт доступ к стеку вызовов (стеку ошибки), позволяет узнать о том, что происходило в программе на момент возникновения ошибки, вызов каких функций предшествовал её появлению. Трассировка стека вызовов может оказаться важнейшей частью процесса отладки. И, несмотря на то, что свойство stack стандартным не является, оно доступно во всех современных браузерах.
Вот как выглядит свойство stack объекта ошибки в Chrome 46.
"Error: foobarn at new bar (<anonymous>:241:11)n at foo (<anonymous>:245:5)n at <anonymous>:250:5n at <anonymous>:251:3n at <anonymous>:267:4n at callFunction (<anonymous>:229:33)n at <anonymous>:239:23n at <anonymous>:240:3n at Object.InjectedScript._evaluateOn (<anonymous>:875:140)n at Object.InjectedScript._evaluateAndWrap (<anonymous>:808:34)"
Перед нами — неотформатированная строка. Когда содержимое этого свойства представлено в таком виде, работать с ним неудобно. Вот как то же самое будет выглядеть после форматирования.
Error: foobar
at new bar (<anonymous>:241:11)
at foo (<anonymous>:245:5)
at callFunction (<anonymous>:229:33)
at Object.InjectedScript._evaluateOn (<anonymous>:875:140)
at Object.InjectedScript._evaluateAndWrap (<anonymous>:808:34)
Теперь, после форматирования, стек ошибки выглядит куда понятнее, сразу становится ясно, почему свойство stack весьма важно при отладке ошибок.
Однако, и тут не всё гладко. Свойство stack не стандартизировано, оно по-разному реализовано в различных браузерах. Вот, например, как выглядит стек ошибки в Internet Explorer 11.
Error: foobar
at bar (Unknown script code:2:5)
at foo (Unknown script code:6:5)
at Anonymous function (Unknown script code:11:5)
at Anonymous function (Unknown script code:10:2)
at Anonymous function (Unknown script code:1:73)
Можно заметить, в сравнении с предыдущим примером, то, что здесь не только используется другой формат представления стековых кадров, но и то, что здесь приведено меньше данных по каждому кадру. Например, Chrome выявляет случаи использования ключевого слова new и даёт более подробные сведения о других событиях (в частности, о вызовах функций._evaluateOn и ._evaluateAndWrap). При этом тут мы сравнили лишь то, что выдают IE и Chrome. В других браузерах использованы собственные подходы к выводу данных о стеке и к подбору сведений, включаемых в эти данные.
Для того чтобы привести всё это к единообразному виду, можно воспользоваться сторонними инструментами. Например, в raven-js для этого используется TraceKit. Той же цели служит stacktrace.js и некоторые другие проекты.
Особенности поддержки window.onerror различными браузерами
Событие windows.onerror существует в браузерах уже довольно давно. В частности, его можно обнаружить в IE6 и в Firefox 2. Проблема здесь заключается в том, что все браузеры реализуют windows.onerror по-разному. Например, это касается количества и структуры аргументов, передаваемых обработчикам этого события.
Вот таблица, в которой приведены сведения об аргументах, передаваемых обработчику onerror в основных браузерах.
| Браузер | message | url | lineNo | colNo | errorObj |
| Firefox | Есть | Есть | Есть | Есть | Есть |
| Chrome | Есть | Есть | Есть | Есть | Есть |
| Edge | Есть | Есть | Есть | Есть | Есть |
| IE 11 | Есть | Есть | Есть | Есть | Есть |
| IE10 | Есть | Есть | Есть | Есть | Нет |
| IE 9,8 | Есть | Есть | Есть | Нет | Нет |
| Safari 10 и выше | Есть | Есть | Есть | Есть | Есть |
| Safari 9 | Есть | Есть | Есть | Есть | Нет |
| Android Browser 4.4 | Есть | Есть | Есть | Есть | Нет |
Вероятно, нет ничего удивительного в том, что Internet Explorer 8, 9, и 10 имеют ограниченную поддержку onerror. Однако необычным может показаться то, что в браузере Safari поддержка объекта ошибки появилась лишь в 10-й версии, вышедшей в 2016 году. Кроме того, существуют устаревшие мобильные устройства, использующие стандартный браузер Android, который также не поддерживает объект ошибки. В современных версиях Android этот браузер заменён на Chrome Mobile.
Если в нашем распоряжении нет объекта ошибки, то нет и данных о трассировке стека. Это означает, что браузеры, не поддерживающие объект ошибки, не дают, в стандартном сценарии использования обработчика onerror, сведений о стеке. А это, как мы уже говорили, очень важно.
Разработка полифилла для window.onerror с использованием конструкции try/catch
Для того чтобы получить сведения о стеке в браузерах, не поддерживающих передачу в обработчик onerror объекта ошибки, можно воспользоваться следующим приёмом. Код можно обернуть в конструкцию try/catch и перехватывать ошибки самостоятельно. Полученный в результате объект ошибки будет содержать, во всех современных браузерах, то, что нам нужно — свойство stack.
Взгляните на код вспомогательного метода invoke(), который вызывает заданный метод объекта, передавая ему массив аргументов.
function invoke(obj, method, args) {
return obj[method].apply(this,args);
}
Вот как им пользоваться.
invoke(Math, 'max', [1,2]); // вернёт 2
Вот тот же invoke(), но теперь вызов метода обёрнут в try/catch, что позволяет перехватывать возможные ошибки.
function invoke(obj, method, args) {
try {
return obj[method].apply(this,args);
} catch(e) {
captureError(e);// сообщить об ошибке
throw e;// повторно выбросить ту же ошибку
}
}
invoke(Math,'highest',[1,2]); // выбрасывает ошибку, сигнализирующую об отсутствии метода Math.highest
Конечно, весьма накладно вручную добавлять подобные конструкции во все места, где они могут понадобиться. Эту задачу можно упростить, создав универсальную вспомогательную функцию.
function wrapErrors(fn) {
//не оборачивайте функции больше одного раза
if(!fn.__wrapped__) {
fn.__wrapped__ = function() {
try{
return fn.apply(this,arguments);
}catch(e){
captureError(e);// сообщить об ошибке
throw e;// повторно выбросить ту же ошибку
}
};
}
return fn.__wrapped__;
}
var invoke = wrapErrors(function(obj, method, args) {
returnobj[method].apply(this,args);
});
invoke(Math,'highest',[1,2]);//ошибка, нет метода Math.highest
Так как в JavaScript используется однопоточная модель выполнения кода, эту обёртку нужно использовать только с теми вызовами функций, которые находятся в начале новых стеков. Нет необходимости оборачивать в неё абсолютно все вызовы функций.
В результате оказывается, что эту функцию нужно использовать в следующих местах:
- Там, где начинается работа приложения (например, при использовании jQuery, в функции
$(document).ready) - В обработчиках событий (например, в
addEventListenerили в конструкциях вида$.fn.click) - В коллбэках, вызываемых по событиям таймера (например, это
setTimeoutилиrequestAnimationFrame)
Вот пример использования функции wrapErrors.
$(wrapErrors(function () {// начало работы приложения
doSynchronousStuff1();// здесь обёртка не нужна
setTimeout(wrapErrors(function () {
doSynchronousStuff2();// здесь тоже не нужна обёртка
}));
$('.foo').click(wrapErrors(function () {
doSynchronousStuff3();// и здесь не нужна
}));
}));
Такие конструкции можно добавлять в код самостоятельно, но это слишком трудоёмкая задача. В качестве удобной альтернативы в подобных ситуациях можно рассмотреть библиотеки для работы с ошибками, которые, например, имеют механизмы, снабжающие addEventListener и setTimeout средствами для перехвата ошибок.
Передача ошибок на сервер
Итак, теперь в нашем распоряжении есть средства для перехвата сведений об ошибках либо с помощью windows.onerror, либо с помощью вспомогательных функций, основанных на try/catch. Эти ошибки возникают на стороне клиента, и, после их перехвата, нам хотелось бы с ними разобраться и принять меры к их устранению. Для этого их нужно передать на наш сервер. Для того чтобы это сделать, нужно подготовить веб-сервис, который принимал бы сведения об ошибках по HTTP, после чего каким-то образом сохранял бы их для дальнейшей обработки, скажем — писал бы в лог-файл или в базу данных.
Если этот веб-сервис расположен на том же домене, что и веб-приложение, достаточно будет воспользоваться XMLHttpRequest. В следующем примере показано использование функции для выполнения AJAX-запросов из jQuery для передачи данных на сервер.
function captureError(ex){
var errorData = {
name:ex.name,// например: ReferenceError
message:ex.line,// например: x is undefined
url:document.location.href,
stack:ex.stack// строка трассировки стека; помните, в разных браузерах они разные!
};
$.post('/logger/js/',{
data:errorData
});
}
Учитывайте то, что если нужно, для отправки сведений об ошибках на сервер, выполнять кросс-доменные запросы, вам придётся позаботиться о поддержке таких запросов.
Итоги
Вы ознакомились с основами создания службы для перехвата ошибок и отправки сведений о них на сервер. В частности, здесь мы рассмотрели следующие вопросы:
- Особенности события
onerrorи его поддержка в различных браузерах. - Использование механизма
try/catchдля получения сведений о стеке вызовов в тех случаях, когдаonerrorне поддерживает работу с объектом ошибки. - Передача данных об ошибках на сервер разработчика.
Узнав о том, как работают вышеописанные механизмы, вы обзавелись базовыми знаниями, которые позволят приступить к созданию собственной системы для работы с ошибками, уточнив в ходе работы дополнительные детали. Пожалуй, этот сценарий особенно актуален для тех случаев, когда речь идёт о некоем приложении, в котором, скажем, из соображений безопасности, не планируется использовать сторонние библиотеки. Если же ваше приложение допускает использование стороннего кода, вы вполне можете подобрать подходящий инструмент для мониторинга JS-ошибок. Среди таких инструментов — Sentry, Rollbar, TrackJS и другие подобные проекты.
Уважаемые читатели! Какими инструментами для мониторинга JS-ошибок вы пользуетесь?

Вчера всё работало, а сегодня не работает / Код не работает как задумано
или
Debugging (Отладка)
В чем заключается процесс отладки? Что это такое?
Процесс отладки состоит в том, что мы останавливаем выполнения скрипта в любом месте, смотрим, что находится в переменных, в функциях, анализируем и переходим в другие места; ищем те места, где поведение отклоняется от правильного.
Будет рассмотрен пример с Сhrome, но отладить код можно и в любом другом браузере и даже в IDE.
Открываем инструменты разработчика. Обычно они открывается по кнопке F12 или в меню Инструменты → Инструменты Разработчика. Выбираем вкладку Sources

Цифрами обозначены:
- Иерархия файлов, подключенных к странице (js, css и другие). Здесь можно выбрать любой скрипт для отладки.
- Сам код.
- Дополнительные функции для контроля.
В секции №2 в левой части на любой строке можно кликнуть ЛКМ, тем самым поставив точку останова (breakpoint — брейкпойнт). Это то место, где отладчик автоматически остановит выполнение JavaScript, как только до него дойдёт. Количество breakpoint’ов не ограничено. Можно ставить везде и много. На изображении выше отмечен зеленым цветом.
В остановленном коде можно посмотреть текущие значения переменных, выполнять различные команды и др.
А во вкладке Breakpoints можно:
- На время выключить брейкпойнт(ы)
- Удалить брейкпойнт(ы), если не нужен
- Быстро перейти на место кода, где стоит брейкпойнт кликнув на текст.
Запускаем отладку
В данном случае, т.к. функция выполняется сразу при загрузке страницы, то для активации отладчика достаточно её перезагрузить. В ином случае, для активации требуется исполнить действие, при котором произойдет исполнение нужного участка кода (клик на кнопку, заполнение инпута данными, движение мыши и другие действия)
В данном случае после перезагрузки страницы выполнение «заморозится» на 4 строке:

-
Вкладка Watch — показывает текущие значения любых переменных и выражений. В любой момент здесь можно нажать на
+, вписать имя любой переменной и посмотреть её значение в реальном времени. Напримерdataилиnums[0], а можно иnums[i]иitem.test.data.name[5].info[key[1]]и т.д. -
Вкладка Call Stack — стэк вызовов, все вложенные вызовы, которые привели к текущему месту кода. На данный момент отладчик стоит в функции
getSum, 4 строка. -
Вкладка Scope Variables — переменные. На текущий момент строки ниже номера 4 ещё не выполнилась, поэтому
sumиoutputравныundefined.
В Local показываются переменные функции: объявленные через var и параметры.
В Global – глобальные переменные и функции.
Процесс
Для самого процесса используются элементы управления (см. изображение выше, выделено зеленым прямоугольником)
(F8) — продолжить выполнение. Продолжает выполнения скрипта с текущего момента. Если больше нет других точек останова, то отладка заканчивается и скрипт продолжает работу. В ином случае работа прерывается на следующей точке останова.
(F10) — делает один шаг не заходя внутрь функции. Т.е. если на текущей линии есть какая-то функция, а не просто переменная со значением, то при клике данной кнопки, отладчик не будет заходить внутрь неё.
(F11) — делает шаг. Но в отличие от предыдущей, если есть вложенный вызов (например функция), то заходит внутрь неё.
(Shift+F11) — выполняет команды до завершения текущей функции. Удобна, если случайно вошли во вложенный вызов и нужно быстро из него выйти, не завершая при этом отладку.
— отключить/включить все точки останова
— включить/отключить автоматическую остановку при ошибке. Если включена, то при ошибке в коде он скрипт остановится автоматически и можно посмотреть в отладчике текущие значения переменных, проанализировать и принять меры по устранению.
…
Итак, в текущем коде видно значение входного параметра:
data = "23 24 11 18"— строка с данными через пробелnums = (4) ["23", "24", "11", "18"]— массив, который получился из входной переменной.
Если нажмем F10 2 раза, то окажемся на строке 7; во вкладках Watch, Scope > Local и в самой странице с кодом увидим, что переменная sum была инициализирована и значение равно 0.
Если теперь нажмем F11, то попадем внутрь функции-замыкания nums.forEach

Нажимая теперь F10 пока не окончится цикл, можно будет наблюдать, как на каждой итерации цикла постоянно изменяются значения num и sum. Тем самым мы можем проследить шаг за шагом весь процесс изменения любых переменных и значений на любом этапе, который интересует.
Дальнейшие нажатия F10 переместит линию кода на строки 11, 12 и, наконец, 15.
Дополнительно
-
Остановку можно инициировать принудительно без всяких точек останова, если непосредственно в коде написать ключевое слово
debugger:function getSum(data) { ... debugger; // <-- отладчик остановится тут ... } -
Если нажать ПКМ на строке с брейкпойнтом, то это позволит еще более тонко настроить условие, при котором на данной отметке надо остановиться.
В функции выше, например, нужно остановиться только когдаsumпревысит значение 20.
Это удобно, если останов нужен только при определённом значении, а не всегда (особенно в случае с циклами).
Больше информации о возможностях инструментов например Chrome — можно прочитать здесь
Дополнительно 2
Принудительную отладку можно инициировать событием, происходящим на странице/элементах. Это полезно, если не знаешь где обрабатывающая функция находится.
Пример для Chrome:
Нажимаем F12, заходим на вкладку Sources и в функциях контроля видим вкладку Event Listener Breakpoints, в которой можно назначить в качестве триггера любые события, при которых исполнение скрипта будет остановлено.
На изображении ниже выбран пункт на событие onchange элементов.

Для Firefox:
Если функция инлайновая, например
<input type="checkbox" onchange="testFunction(this);" />
то можно зайти в Инспектор, найти тот самый элемент, в котором прописано событие и обнаружить рядом значок em:

Кликнув на него, как утверждает developer.mozilla.org/ru/docs можно увидеть строчки:

где можно увидеть события, навешанные на элемент, скрипт, строку, возможность нажать на паузу и т.д.
В других случаях, а также если кнопка паузы не обнаружена, то на вкладке Debugger(отладчик) надо найти стрелку, при наведении на которую будет написано «Events». Там должно быть событие выделенного элемента.
А вот таких полезных вкладок как у Chrome к сожалению у Firefox там нет.

Как только начинаешь программировать, наступает неприятный момент, когда код написан, но по каким-то причинам он отказывается работать. При всем этом браузер ведет себя нормально, сообщений, что присутствуют ошибки, не выдает.
Как отследить в таком случае банальные опечатки и ошибки в javascript?
В данном видео разберем, три метода при помощи которых можно отловить синтаксические ошибки JavaScript. Более детально покажу, как пользоваться консолью браузера и отслеживать в ней ошибки, покажу как при помощи функции alert(); можно проверить работоспособность файлов JavaScript и в некоторых местах отловить ошибки, так же поговорим о сторонних сервисов, которые помогают отловить ошибки в коде JS.
Самый неприятный момент наступает тогда, когда вы пытаетесь просмотреть в браузере страницу, содержащую код JavaScript и нечего не происходит. Все это, потому что в большинстве браузеры настроена на тихое игнорирование ошибок JS, по этому вы зачастую даже не увидите диалогового окна, что присутствует ошибка. И в принципе это замечательно, так как мало кому хотелось бы из-за ошибки останавливать сценарий работы Веб-страницы.
Акцент нужно сделать не на том, чтобы избавится от ошибок, так как это в принципе не возможно, даже опытные программисты допускают ошибки или простые банальные опечатки, это неотъемлемая часть данной работы.
Суть в том, что бы научится отлавливать и находить ошибки самостоятельно за короткий период времени. В данном видео познакомимся с тремя распространенными вариантами как найти ошибки в самом коде.
Итак, как определить, что JS работает неправильно? На самом деле есть множество способов отслеживания ошибок в коде, их логический разбор, дебаг кода и с ними я Вас познакомлю в будущем, а сейчас я вам покажу основные три примера, которые облегчать вам программирование на данном этапе.
У каждого из примера есть свои плюсы и минусы, каким пользоваться выбирать вам самостоятельно, как по мне я использую все три варианта, они идеально подходят на начальном уровне и дополняют друг друга.
1 Отлавливать ошибки через консоль браузера
В каждом браузере есть свой механизм отслеживания ошибок JS и они умеют записывать ошибки в отдельное окно, которое называется консолью ошибок. В зависимости, каким браузером пользуетесь, вызов данной консоли может у них отличается.
Для примера как это все работает, напишем простенькую программу. Где по нажатию на кнопку будем менять цвет фона страницы.
<script>
alert("Ошибки не найдено");
var colorArray = ["#9C3A2E", "#FC1D0F", "#5f76fa", "#FAD5BB", "#01c1c1"]; // создаем массив с цветами фона
var i = 0;
function changeColor() {
document.body.style.background = colorArray[i];
i ++;
if (i > colorArray.length - 1) {
i = 0;
}
}
</script>
<button onclick="changeColor();">Изменить цвет</button>
Теперь нажимаем на копку и на страничке нечего не происходит, так как она содержит ошибку. Консоль браузера поможет отловить основные опечатки, которые непременно будут присутствовать при начальном этапе программирования.
Консоль в браузере Ghrome вызывается при помощи кнопки F12, а также ctrl+shrift+I или вызвать из настроек, перейти в настройки меню, инструменты разработчиков.
Как только консоль отобразилась, нам сразу показывается информация о найденных ошибках, которые на первый взгляд не совсем понятны. По сути, эта ошибка синтаксическая связанная с какой-то опечаткой – ошибкой в коде программы. Строка Unexpected token означает, что браузер обнаружил недопустимый или пропущенный символ. В данном случае если мы внимательно посмотрим, то увидим, что не стоит переменная, которую нужно увеличивать.
В консоли также выводится имя файла с ошибкой и номер строки, что помогает ее обнаружению. Щелкнув мышью по имени файла, мы увидим выделенную строку с ошибкой.
Консоль в браузере Internet Explorer вызывается при помощи кнопки F12, или из настроек браузера, инструменты разработчика. По аналоги также как и в Ghrome мы видим, что присутствует ошибка, какого рода и на какой строке.
В браузер Firefof от компании Mozilla также предусмотрена консоль отслеживания ошибок. Что бы ее отобразить можно воспользоваться сочетанием клавиш ctrl+shrift+I или вызвать это меню из настроек браузера.
В принципе как вы заметили, механизм практически везде схож, и выбор чем пользоваться остается только за вами.
2 Проверка подключаемых файлов при помощи функции alert
Не спроста в предыдущем уроке я продемонстрировал как работает функция alert(); ведь благодаря ей можно успешно отследить какие скрипты подключены и в некоторых случаях отловить в них ошибки.
Для примера создадим файл и подключим его к страничке Index.html. Мы незнаем на сколько правильно он работает, и подключен ли он вообще, для этого разместим в нем простенький скрипт и загрузим страницу.
Код будет выводить в цикле заданное количество слов.
<script>
var timesHello = 5;
alert("Ошибки нет");
for (var i = 0; i < timesHello; i++){
document.write ("Привет ");
}
</script>
Сделает преднамеренную опечатку, и загрузим страницу. Вроде она заработала, но вывод работы скрипта не увидели. Так вот в таких случаях можно воспользоваться функцией alert(); достаточно ее вызвать в файле что бы убедится что он подключен. То есть, если появилось всплывающее окно значит файл успешно.
При помощи подбора мы прописываем данную функцию после каждой строки тем самым сможем отловить ошибку в самом коде. Но не всегда этот метод будет удобен, если у Вас код большой, в несколько сот, а то и тысяч строк, понадобится много времени что бы перебрать эту громаду, по этому в данном случае она используется в большей мере для определенных участков кода где ожидаема ошибка или протестить правильность подключение скрипта.
3 Воспользуемся сторонними сервисами
В просторах интернета есть множества сервисов при помощи которых можно проверить синтаксический анализ кода.
Первый разберем
javascript валидатор.Если мы выяснили что код не работает, копируем его и вставляем в поле для валидации, жмем проверить и система выдает результат, в каком месте и какого характера присутствует ошибка.
Второй вариант который можно рассмотреть это замечательный
парсер, который может создать синтаксическое дерево для скрипта. И если этот парсер сможет разложить скрипт, значит в программе нет синтетических ошибок.
По аналогии копируем код в представленное окно, и система автоматически его пропарсит, но если в нем будет, хоть малейшая опечатка получим такую ошибку с номером строки.
К сожалению, список причин, по которым сценарий может работать неправильно, очень велик, от простых опечаток до логических ошибок. При начальном этапе программирования в большей степени будут преобладать именно опечатки. Где то забудете поставить кавычки, точку с запитой, не закроете элементарно круглые скобки или неправильно запишите команду. Будьте особенно внимательны при наборе кода и не забывайте о методах отлова ошибок.
Каждый из этих методов по свое своему уникален, везде свои плюсы и минусы, в комментариях пишите какими пользуетесь вы, каким способом отлавливаете ошибки в javaScripte. Обязательно поделитесь своим опытом.
Видео подготовил Горелов Денис, до встречи в следующем уроке, где начнем работать с грамматикой на JS.
Оставить комментарий:
In this article, we will learn to log script Errors using JavaScript. It is useful in case where scripts from any other source run/executes under the website (Ex – in an iframe) or cases where any of your viewer makes use of Browser’s Console trying to alter the script or you want to monitor your JS code for errors it may get into when put in production.
Approach: We will learn 3 methods to accomplish this task — all following the same approach but different way to implement the approach and extract the useful Information. Use whichever suits best in your case.
- window.addEventListener(“error”, //yourErrorLoggerFunction) — Attach an error event to the window object (DOM).
- document.querySelector(‘body’).onerror= //yourErrorLoggerFunction — Error Listener in the body tag -> Works both in Same Origin or Cross-Scripts of Same Origin.
- Create a script element and append it to the body tag of the source.
All three method can be implemented both in same source or any external source.
Note — Make sure this is the first thing to be executed by the browser (In case you are not sure it is strongly recommended to use Method 2).
Method 1: In this method, we will attach an Event Listener to our window object. Whenever any script error occurs, the attached errorLogger Function is triggered and inside the errorLoggerFunction we will extract all useful information from the event Object that our errorLogger function receives.
Javascript
window.addEventListener("error", errorLog);
function errorLog(event) {
let msg = source = lineno = colno
= error = time = "";
event.preventDefault();
msg = event.message;
source = event.filename;
lineno = event.lineno;
colno = event.colno;
error = event.error;
time = event.time;
}
Find the HTML Code (Method 1):
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content=
"width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<p style="font-size:20px;color:rgb(219, 0, 0)">
No Errors
</p>
<script>
// Make Sure addEventListener is the
// First thing to be Executed
window.addEventListener("error", errorLog);
// String to Prepare Error Paragraph
let errorString = "";
const pElement = document.querySelector('p');
// This console.log creates Error
console.log(a)
function errorLog(event) {
let msg = source = lineno
= colno = error = time = "";
event.preventDefault();
msg = event.message;
source = event.filename;
lineno = event.lineno;
colno = event.colno;
error = event.error;
time = event.time;
errorString = `Script Error was Found<br>
Message-: ${msg}<br>Source Info-: ${source}
<br>Line No-: ${lineno}<br>Column No-:
${colno}<br>Error Info-: ${error}<br>Time
at which Error Occurred-: ${time}ms after
Page Load<br><br><br><br>`;
pElement.innerHTML = errorString;
}
</script>
</body>
</html>
Example -METHOD 1
Method 2: In this method, we will make use of onerror Event Handler.
- Step 1 — Get the body tag of that HTML Markup whichever is your case either the external HTML Markup or the Markup of the Currently loading Page.
- Step 2 — Now when we have got the body tag in a const identifier let say const bodyTag we will add onerror Event Handler to the bodyTag.
Javascript
const bodyTag =
bodyTag.onerror = errorLogger;
function errorLogger(msg, url,
lineNo, columnNo, error) {
}
Did you notice the difference in the logger function of the above two methods — Usually in case of firing of any event, the function attached to it receives an event object but in this case (method 2) we receive a pre-extracted information (For Detailed Reason Refer MDN WebDocs).
Note — Keep in mind the order of error information received in the loggerFunction- Total 5 Information — Sequence always being the same.
Find the HTML Code (Method 2):
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content=
"width=device-width, initial-scale=1.0">
</head>
<body onerror="errorLogger(message,
source, lineno, colno, error)">
<p style="font-size:20px;
color:rgb(219, 0, 0)">
</p>
<script>
// You can directly set the onerror
// Attribute or use below 2 lines
// Get body tag of External HTML
// Markup or of your current page
const bodyTag = document.querySelector('body');
// bodyTag.onerror = errorLogger;
let errorString = "";
const pElement = document.querySelector('p');
function errorLogger(msg, url,
lineNo, colNo, error) {
// Now process your Error Information
// as you desire
errorString += `Script Error was Found
<br>Message-: ${msg}<br>URL-: ${url}
<br>Line No-: ${lineNo}<br>Column No-:
${colNo}<br>Error Info-: ${error}<br>
<br><br><br>`;
pElement.innerHTML += errorString;
}
</script>
</body>
</html>
Example – METHOD 2
Method 3: In this method, we will attach a script element to the WebPage. This method is useful in cases when we want error logger to activate at a specific time or in cases of external script execution, etc.
- Step 1 — Get the body tag of the Markup Page you are targeting it.
- Step 2 — Prepare a script element and append it.
Javascript
const htmlDOM =
let errScr = htmlDOM.document
.createElement('script');
errScr.type = 'text/javascript';
errScr.innerText =
`window.addEventListener("error",
errorLog);function errorLog(event)
{
htmlDOM.document.querySelector
('body').append(errScr);
Example – METHOD 3
Explanation of example — Method 3: As you can notice we have used iframe to access index.html of the same Page(Same-Origin) we planted a script in parentPage to logg errors in another WebPage.
Warning — Although you will be blocked but Beware Using these Methods in case of CROSS-SCRIPTING (CORS) (Scripts of Different Origin) without written permission from the owner of that Page.
Find the HTML Code (Method 3):
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content=
"width=device-width, initial-scale=1.0">
</head>
<body>
<iframe src="index.html"
frameborder="0">
</iframe>
<script>
const htmlDOM = document
.querySelector('iframe').contentDocument;
let errScr = htmlDOM.createElement('script');
errScr.type = 'text/javascript';
errScr.innerText = `console.log("Script
Loaded");window.addEventListener("error",
errorLog);function errorLog(event){
console.log("Error can be Processed
Now....Use the eventObject as used
in method-1")}`;
htmlDOM.querySelector('body').append(errScr);
</script>
</body>
</html>
Important Note –
- You can make use of both innerText and innerHTML to write the js script but we recommend using innerText as presence any HTML entity in the script would create any unwanted error.
- In method 3, the target and the way of getting them may differ in different cases. So the code used in the implementation of this method may differ slightly but the approach would remain same.
In this article, we will learn to log script Errors using JavaScript. It is useful in case where scripts from any other source run/executes under the website (Ex – in an iframe) or cases where any of your viewer makes use of Browser’s Console trying to alter the script or you want to monitor your JS code for errors it may get into when put in production.
Approach: We will learn 3 methods to accomplish this task — all following the same approach but different way to implement the approach and extract the useful Information. Use whichever suits best in your case.
- window.addEventListener(“error”, //yourErrorLoggerFunction) — Attach an error event to the window object (DOM).
- document.querySelector(‘body’).onerror= //yourErrorLoggerFunction — Error Listener in the body tag -> Works both in Same Origin or Cross-Scripts of Same Origin.
- Create a script element and append it to the body tag of the source.
All three method can be implemented both in same source or any external source.
Note — Make sure this is the first thing to be executed by the browser (In case you are not sure it is strongly recommended to use Method 2).
Method 1: In this method, we will attach an Event Listener to our window object. Whenever any script error occurs, the attached errorLogger Function is triggered and inside the errorLoggerFunction we will extract all useful information from the event Object that our errorLogger function receives.
Javascript
window.addEventListener("error", errorLog);
function errorLog(event) {
let msg = source = lineno = colno
= error = time = "";
event.preventDefault();
msg = event.message;
source = event.filename;
lineno = event.lineno;
colno = event.colno;
error = event.error;
time = event.time;
}
Find the HTML Code (Method 1):
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content=
"width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<p style="font-size:20px;color:rgb(219, 0, 0)">
No Errors
</p>
<script>
// Make Sure addEventListener is the
// First thing to be Executed
window.addEventListener("error", errorLog);
// String to Prepare Error Paragraph
let errorString = "";
const pElement = document.querySelector('p');
// This console.log creates Error
console.log(a)
function errorLog(event) {
let msg = source = lineno
= colno = error = time = "";
event.preventDefault();
msg = event.message;
source = event.filename;
lineno = event.lineno;
colno = event.colno;
error = event.error;
time = event.time;
errorString = `Script Error was Found<br>
Message-: ${msg}<br>Source Info-: ${source}
<br>Line No-: ${lineno}<br>Column No-:
${colno}<br>Error Info-: ${error}<br>Time
at which Error Occurred-: ${time}ms after
Page Load<br><br><br><br>`;
pElement.innerHTML = errorString;
}
</script>
</body>
</html>
Example -METHOD 1
Method 2: In this method, we will make use of onerror Event Handler.
- Step 1 — Get the body tag of that HTML Markup whichever is your case either the external HTML Markup or the Markup of the Currently loading Page.
- Step 2 — Now when we have got the body tag in a const identifier let say const bodyTag we will add onerror Event Handler to the bodyTag.
Javascript
const bodyTag =
bodyTag.onerror = errorLogger;
function errorLogger(msg, url,
lineNo, columnNo, error) {
}
Did you notice the difference in the logger function of the above two methods — Usually in case of firing of any event, the function attached to it receives an event object but in this case (method 2) we receive a pre-extracted information (For Detailed Reason Refer MDN WebDocs).
Note — Keep in mind the order of error information received in the loggerFunction- Total 5 Information — Sequence always being the same.
Find the HTML Code (Method 2):
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content=
"width=device-width, initial-scale=1.0">
</head>
<body onerror="errorLogger(message,
source, lineno, colno, error)">
<p style="font-size:20px;
color:rgb(219, 0, 0)">
</p>
<script>
// You can directly set the onerror
// Attribute or use below 2 lines
// Get body tag of External HTML
// Markup or of your current page
const bodyTag = document.querySelector('body');
// bodyTag.onerror = errorLogger;
let errorString = "";
const pElement = document.querySelector('p');
function errorLogger(msg, url,
lineNo, colNo, error) {
// Now process your Error Information
// as you desire
errorString += `Script Error was Found
<br>Message-: ${msg}<br>URL-: ${url}
<br>Line No-: ${lineNo}<br>Column No-:
${colNo}<br>Error Info-: ${error}<br>
<br><br><br>`;
pElement.innerHTML += errorString;
}
</script>
</body>
</html>
Example – METHOD 2
Method 3: In this method, we will attach a script element to the WebPage. This method is useful in cases when we want error logger to activate at a specific time or in cases of external script execution, etc.
- Step 1 — Get the body tag of the Markup Page you are targeting it.
- Step 2 — Prepare a script element and append it.
Javascript
const htmlDOM =
let errScr = htmlDOM.document
.createElement('script');
errScr.type = 'text/javascript';
errScr.innerText =
`window.addEventListener("error",
errorLog);function errorLog(event)
{
htmlDOM.document.querySelector
('body').append(errScr);
Example – METHOD 3
Explanation of example — Method 3: As you can notice we have used iframe to access index.html of the same Page(Same-Origin) we planted a script in parentPage to logg errors in another WebPage.
Warning — Although you will be blocked but Beware Using these Methods in case of CROSS-SCRIPTING (CORS) (Scripts of Different Origin) without written permission from the owner of that Page.
Find the HTML Code (Method 3):
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content=
"width=device-width, initial-scale=1.0">
</head>
<body>
<iframe src="index.html"
frameborder="0">
</iframe>
<script>
const htmlDOM = document
.querySelector('iframe').contentDocument;
let errScr = htmlDOM.createElement('script');
errScr.type = 'text/javascript';
errScr.innerText = `console.log("Script
Loaded");window.addEventListener("error",
errorLog);function errorLog(event){
console.log("Error can be Processed
Now....Use the eventObject as used
in method-1")}`;
htmlDOM.querySelector('body').append(errScr);
</script>
</body>
</html>
Important Note –
- You can make use of both innerText and innerHTML to write the js script but we recommend using innerText as presence any HTML entity in the script would create any unwanted error.
- In method 3, the target and the way of getting them may differ in different cases. So the code used in the implementation of this method may differ slightly but the approach would remain same.
Отслеживание js-ошибок с помощью Метрики
Использование системы отслеживания js-ошибок трудно переоценить. Даже на покрытом тестами сайте возникают js-ошибки, важно их найти и починить. Расскажу как искал подходящее решение.
Существует три вида систем отслеживания js-ошибок.
Первый вид
Самописная система. Добавляем компактный js-код на сайт:
window.onerror = function(msg, file, line, col) { new Image().src = '/jserrors/?msg=' + msg + ...; }
Делаем «ручку» для сохранения ошибок, парсим логи.
В лучшем случае, пишем свой интерфейс для анализа ошибок.
Потом занимаемся доработкой и поддержкой.
Второй вид
Платный сервис с расширенными возможностями, но с ограничениями, например, на количество js-ошибок в день или месяц. На любой сайт поставить эту систему не получится, придётся каждый раз выбирать какой из ваших сайтов «достоин» платного сервиса. И не забывать оплачивать услуги.
Нужно будет добавить на сайт внешний скрипт, который отрицательно повлияет на скорость загрузки вашего сайта.
Третий вид
Использовать для сбора ошибок систему аналитики, для этого необходимо установить на сайт её полноценный код отслеживания.
Так как код отслеживания загружается асинхронно, то отправка ошибок будет доступна только после его загрузки. У данной системы должны быть отчёты, которые можно самостоятельно формировать.
Решение
Объединим первый и третий вид. Ошибки будем вручную отправлять в Яндекс.Метрику с помощью компактного js-кода. В Метрике есть необходимые отчёты и инструменты для анализа данных, нет особых ограничений на количество счётчиков и собираемых данных, и она бесплатна.
Для сбора ошибок подойдёт отчёт «Параметры визитов».
-
Заведём отдельный счётчик.
-
Добавим на страницу перед всеми скриптами компактный код:
<script> // Не более 320 байт после минификации. window.onerror = function(msg, file, line, col, err) { // Отсекаем совсем старые браузеры. if (!window.JSON) { return; } var counterId = 12345, // Ваш номер счётчика Метрики. siteInfo = {}, pointer = siteInfo; // Список параметров визитов. path = [ 'JS errors', // 1 уровень msg, // 2 уровень err && err.stack || (file + ':' + line + ':' + col) // 3 уровень // Не хватает параметров? Добавьте ещё! ]; // Преобразуем параметры из плоского в древовидный вид для отчёта. for (var i = 0; i < path.length - 1; i++) { var item = path[i]; pointer[item] = {}; pointer = pointer[item]; } pointer[path[i]] = 1; new Image().src = 'https://mc.yandex.ru/watch/' + counterId + '/?site-info=' + encodeURIComponent(JSON.stringify(siteInfo)) '&rn=' + Math.random(); }; </script>
-
Не забываем указать в коде свой номер счётчика (counterId).
-
Получаем примерно такой отчёт:

Структуру и порядок параметров в отчёте можно менять на лету, а также добавлять новые параметры.
Давайте с помощью кнопки «Группировки» добавим браузер и ОС в отчёт.


И ещё один момент, если скрипты на сайте загружаются с другого домена (CDN), то в отчёте, скорее всего, будут видны сообщения вида «Script error» и без стека. Чтобы вернуть сообщениям нормальный вид, необходимо добавить к скриптам атрибут crossorigin="anonymous" и HTTP-заголовок Access-Control-Allow-Origin:"*".
<script src="https://mycdn.com/folder/file.js" crossorigin="anonymous"></script>
Постепенно в отчёте будут появляться ошибки от расширений браузеров, вирусов и внешних скриптов (рекламных систем, кнопок социальных сетей и пр.). Чтобы отделить эти ошибки, добавим проверку домена у скриптов с помощью регулярного выражения.
Дополнительно добавим ограничение на собираемое количество ошибок (не более 5) на странице. Например, однотипные ошибки, возникающие при движении мышки, могут создать сотни запросов в Метрику.
В современных браузерах данные будем отправлять через sendBeacon.
<script> window.onerror = function handler(msg, file, line, col, err) { if (!window.JSON || handler.count > 5) { return; } var counterId = 12345, // Ваш номер счётчика Метрики. siteInfo = {}, pointer = siteInfo, stack = err && err.stack, path = [ // Укажите в регулярном выражении домены, с которых загружаются ваши скрипты и сайт. 'JS ' + (!file || /mysite.ru|cdn.com/.test(file) ? 'in' : 'ex') + 'ternal errors', 'message: ' + msg, stack ? 'stack: ' + stack : (file ? 'file: ' + file + ':' + line + ':' + col : 'nofile'), 'href: ' + location.href ]; for (var i = 0; i < path.length - 1; i++) { var item = path[i]; pointer[item] = {}; pointer = pointer[item]; } pointer[path[i]] = 1; var url = 'https://mc.yandex.ru/watch/' + counterId + '/' + '?site-info=' + encodeURIComponent(JSON.stringify(siteInfo)) + '&rn=' + Math.random(); if (typeof navigator.sendBeacon === 'function') { navigator.sendBeacon(url, ' '); } else { new Image().src = url; } if (handler.count) { handler.count++; } else { handler.count = 1; } }; </script>
И ещё, данные по ошибкам можно получить с помощью API и сделать с ними всё что угодно.
Не забудьте добавить ссылки на отчёты по ошибкам в свою документацию на видное место.
И дать доступ к отчётам остальным разработчикам из группы, чтобы исправление ошибок превратилось в соревнование 🙂
Ссылки:
- GitHub
- Отчёт «Параметры визитов»
- Пример отчёта в Метрике
Сервис Sentry позволяет удаленно мониторить баги в фронтенд-приложениях, написанных на JavaScript.

Попытка устранить проблемы в фронтенд-приложениях на JavaScript может оказаться непростой задачкой, поскольку они возникают в браузере пользователя, к чему, зачастую, у вас нет доступа. Однако, Sentry дает возможность удаленно мониторить баги.
Здесь можно скачать решения, которые рассматривались в этой статье.
Что необходимо
Если вы хотите воспользоваться этими примерами, то вам понадобятся:
- Node.js: Многофункциональный инструмент для разработки, который не является частью приложения. Мы скачивали последнюю LTS-версию (8.12.0)
- Sentry: Либо Аккаунт в сервисе Sentry (можно бесплатно записывать до 10 тыс. багов в месяц) либо установленный локальный Sentry — https://github.com/getsentry/onpremise
Установка на ваш сервер
Чтобы установить Sentry On-Premise на ваши сервер можно пойти 2 путями
-
Собрать rpm и установить их — https://habr.com/ru/post/500632/
-
Воспользоваться официальным установщиком:
Установить на сервер docker и docker-compose
git clone https://github.com/getsentry/onpremise.git
./install.sh
Стандартное использование
Чтобы начать, добавьте с сайта сервиса новый Sentry-проект для приложения. После выбора нужного языка вы получите соответствующую документацию. В нашем случае мы выбрали JavaScript.
Первый пример — стандартный JavaScript. Здесь две кнопки: «Hello» (Привет) и «Error» (Ошибка).
После того, как вы кликнете по кнопке «Hello», экран перезагрузится, а блок try обнаружит и поймает баг. После того, как баг «пойман», отчет об ошибке вручную пересылается на сервис Sentry.
Кнопка «Error» позволяет просто обнаружить баг.
vanilla / index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vanilla</title>
</head>
<body>
<button id="hello">Hello</button>
<button id="error">Error</button>
<div id="output"></div>
<script src="https://browser.sentry-cdn.com/4.0.5/bundle.min.js" crossorigin="anonymous"></script>
<script>
(function () {
'use strict';
Sentry.init({ dsn: 'https://b5bf359072254626aba8e64368e77b7d@sentry.io/1289664' });
var helloEl = document.getElementById('hello');
var errorEl = document.getElementById('error');
var outputEl = document.getElementById('output');
helloEl.addEventListener('click', handleHelloClick);
errorEl.addEventListener('click', handleErrorClick);
function handleHelloClick() {
outputEl.innerHTML = 'Hello World';
try {
throw new Error('Caught');
} catch (err) {
Sentry.captureException(err);
}
}
function handleErrorClick() {
throw new Error('Uncaught');
}
})();
</script>
</body>
</html>
Примечания:
- Sentry устанавливается из CDN и отображается как глобальная переменная
- Чуть раньше мы запустили Sentry в нашем JavaScript-е
Чтобы протестировать этот пример, можем воспользоваться статической веб-серверной платформой Node.js: http-сервером. Заходим в папку, где хранится файл index.html, и вводим (опция с отключает кэширование) следущую строку, чтобы открыть в браузере адрес http://localhost:8080.
Как отображаются пойманные баги
Сперва кликаем кнопку «Hello».

Мы словили баг, поэтому он не всплывет вверх по стеку вызовов, поэтому и не мониторится в консоли. Однако, поскольку мы отсылаем в Sentry отчет о баге вручную, то увидим сообщение о нем в аккаунте.

Примечания:
- Можем проследить, в какой строчке (24) затесался баг
- К тому же, навигационная цепочка отображает действия браузера, что привели к ошибке.
Как отображаются непойманные баги
Кликаем кнопку «Error».

Баг всплывает вверх по стеку вызовов и, таким образом, на консоле отображается сообщение об ошибке. После этого Sentry автоматически мониторит баг, без каких-либо дополнительных действий.

Примечания:
- Мы можем увидеть, в какой строчке (30) затерялся баг
- Не предустмотрена навигационная цепочка (не совсем понимаю, почему)
Как обеспечить безопасность проекта
Вы, возможно, обратили внимание на способ, как мы контролируем страницы, которые могут отправлять отчет об ошибках в наш проект Sentry; при помощи dsn записи. Проблема заключается в том, что вход может увидеть каждый, кто просмотрит исходный код вашей страницы.
Чтобы этого не было, нужно ограничить количество доменов, которые могут заносить в наш проект отчеты об ошибках. В этом примере мы использовали localhost (локальный хост). Данная опция настраивается во вкладке настроек Sentry-проекта, Sentry Project Setting.

Релизы
Если подумать о том, как использовать Sentry в разных вариациях нашего приложения, то здесь нужен некий механизм, который будет помечать ошибки номером версии.
Не нужно ведь, чтобы исправленный нами баг выплыл опять, а мы думали, что не сработало то, что мы пофиксили. Может быть ведь и такое, что пользователь запустил более старую, кэшированную версию приложения.
Чтобы решить проблему, нужно вписать идентификатор релиза (версии) при запуске Sentry.
vanilla / index.html
...
var RELEASE = '0.1.0';
Sentry.init({
dsn: 'https://b5bf359072254626aba8e64368e77b7d@sentry.io/1289664',
release: RELEASE,
});
...
После этого все новые ошибки будут отмечены как release (0.1.0), то есть будут привязаны к правильной версии кода.

Примечания:
- Мы разобрали простой способ как использовать релизы
- Sentry позволяет применять более сложное их использование, которое тесно связано с GitHub. Такая функция дает возможность отслеживать баги до совершения определенных операций.
P.S. Вторая часть более длинная, поэтому она будет в отдельном посте.
P.S Телеграм чат Sentry https://t.me/sentry_ru
P.S. Забыл указать что это перевод поста https://codeburst.io/sentry-error-reporting-by-example-part-1-999b2df11556
Поиск и обработка ошибок
Отладка изначально вдвое сложнее написания кода. Поэтому, если вы пишете код настолько заумный, насколько можете, то по определению вы не способны отлаживать его.
Брайан Керниган и П.Ж.Плауэр, «Основы программного стиля»
Юан-Ма написал небольшую программу, использующую много глобальных переменных и ужасных хаков. Ученик, читая программу, спросил его: «Вы предупреждали нас о подобных техниках, но при этом я нахожу их в вашей же программе. Как это возможно?» Мастер ответил: «Не нужно бежать за поливальным шлангом, если дом не горит».
Мастер Юан-Ма, «Книга программирования».
Программа – это кристаллизованная мысль. Иногда мысли путаются. Иногда при превращении мыслей в программу в код вкрадываются ошибки. В обоих случаях получается повреждённая программа.
Недостатки в программах обычно называют ошибками. Это могут быть ошибки программиста или проблемы в системах, с которыми программа взаимодействует. Некоторые ошибки очевидны, другие – трудноуловимы и могут скрываться в системах годами.
Часто проблема возникает в тех ситуациях, возникновение которых программист изначально не предвидел. Иногда этих ситуаций нельзя избежать. Когда пользователя просят ввести его возраст, а он вводит «апельсин», это ставит программу в непростую ситуацию. Эти ситуации необходимо предвидеть и как-то обрабатывать.
В случае ошибок программистов наша цель ясна. Нам надо найти их и исправить. Таковые ошибки варьируются от простых опечаток, на которые компьютер пожалуется сразу же, как только увидит программу, до скрытых ошибок в нашем понимании того, как программа работает, которые приводят к неправильным результатам в особых случаях. Ошибки последнего рода можно искать неделями.
Разные языки по-разному могут помогать вам в поиске ошибок. К сожалению, JavaScript находится на конце этой шкалы, обозначенном как «вообще почти не помогает». Некоторым языкам надо точно знать типы всех переменных и выражений ещё до запуска программы, и они сразу сообщат вам, если типы использованы некорректно. JavaScript рассматривает типы только во время исполнения программ, и даже тогда он разрешает делать не очень осмысленные вещи без всяких жалоб, например
На некоторые вещи JavaScript всё-таки жалуется. Написание синтаксически неправильной программы сразу вызовет ошибку. Другие ошибки, например вызов чего-либо, не являющегося функцией, или обращение к свойству неопределённой переменной, возникнут при выполнении программы, когда она сталкивается с такой бессмысленной ситуацией.
Но часто ваши бессмысленные вычисления просто породят NaN (not a number) или undefined. Программа радостно продолжит, будучи уверенной в том, что она делает что-то осмысленное. Ошибка проявит себя позже, когда такое фиктивное значение уже пройдёт через несколько функций. Она может вообще не вызвать сообщение об ошибке, а просто привести к неправильному результату выполнения. Поиск источника таких проблем – сложная задача.
Процесс поиска ошибок (bugs) в программах называется отладкой (debugging).
Строгий режим (strict mode)
Строгий режим (strict mode)
JavaScript можно заставить быть построже, переведя его в строгий режим. Для этого наверху файла или тела функции пишется «use strict». Пример:
function canYouSpotTheProblem() {
for (counter = 0; counter < 10; counter++)
console.log(«Всё будет офигенно»);
// → ReferenceError: counter is not defined
Обычно, когда ты забываешь написать var перед переменной, как в примере перед counter, JavaScript по-тихому создаёт глобальную переменную и использует её. В строгом режиме выдаётся ошибка. Это очень удобно. Однако, ошибка не выдаётся, когда глобальная переменная уже существует – только тогда, когда присваивание создаёт новую переменную.
Ещё одно изменение – привязка this содержит undefined в тех функциях, которые вызывали не как методы. Когда мы вызываем функцию не в строгом режиме, this ссылается на объект глобальной области видимости. Поэтому если вы случайно неправильно вызовете метод в строгом режиме, JavaScript выдаст ошибку, если попытается прочесть что-то из this, а не будет радостно работать с глобальным объектом.
К примеру, рассмотрим код, вызывающий конструктор без ключевого слова new, в случае чего this не будет ссылаться на создаваемый объект.
function Person(name) { this.name = name; }
var ferdinand = Person(«Евлампий»); // ой-вэй
Некорректный вызов Person успешно происходит, но возвращается как undefined и создаёт глобальную переменную name. В строгом режиме всё по-другому:
function Person(name) { this.name = name; }
// Опаньки, мы ж забыли ‘new’
var ferdinand = Person(«Евлампий»);
// → TypeError: Cannot set property ‘name’ of undefined
Нам сразу сообщают об ошибке. Очень удобно.
Строгий режим умеет ещё кое-что. Он запрещает вызывать функцию с несколькими параметрами с одним и тем же именем, и удаляет некоторые потенциально проблемные свойства языка (например, инструкцию with, которая настолько ужасна, что даже не обсуждается в этой книге).
Короче говоря, надпись «use strict» перед текстом программы редко причиняет проблемы, зато помогает вам видеть их.
Если язык не собирается помогать нам в поиске ошибок, приходится искать их сложным способом: запуская программу и наблюдая, делает ли она что-то так, как надо.
Делать это вручную, снова и снова – верный способ сойти с ума. К счастью, часто возможно написать другую программу, которая автоматизирует проверку вашей основной программы.
Для примера вновь обратимся к типу Vector.
Vector.prototype.plus = function(other) {
return new Vector(this.x + other.x, this.y + other.y);
Мы напишем программу, которая проверит, что наша реализация Vector работает, как нужно. Затем после каждого изменения реализации мы будем запускать проверочную программу, чтобы убедиться, что мы ничего не сломали. Когда мы добавим функциональности (к примеру, новый метод) к типу Vector, мы добавим проверок этой новой функциональности.
var p1 = new Vector(10, 20);
var p2 = new Vector(—10, 5);
if (p1.x !== 10) return «облом: значение x не то»;
if (p1.y !== 20) return «облом: значение y не то»;
if (p2.x !== —10) return «облом: отрицательное значение x не то»;
if (p3.x !== 0) return «облом: результат сложения x не тот»;
if (p3.y !== 25) return «облом: результат сложения y не тот»;
console.log(testVector());
Написание таких проверок приводит к появлению повторяющегося кода. К счастью, есть программные продукты, помогающие писать наборы проверок при помощи специального языка, приспособленного именно для написания проверок. Их называют testing frameworks.
Когда вы заметили проблему в программе (она ведёт себя неправильно и выдаёт ошибки), самое время выяснить, в чём проблема.
Иногда это очевидно. Сообщение об ошибке наводит вас на конкретную строку программы, и если вы прочтёте описание ошибки и эту строку, вы часто сможете найти проблему.
Но не всегда. Иногда строчка, приводящая к ошибке, просто оказывается первым местом, где некорректное значение, полученное где-то ещё, используется неправильно. Иногда вообще нет сообщения об ошибке – есть просто неверный результат. Если вы делали упражнения из предыдущих глав, вы наверняка попадали в такие ситуации.
Следующий пример пробует преобразовать число заданной системы счисления в строку, отнимая последнюю цифру и совершая деление, чтобы избавиться от этой цифры. Но дикий результат, выдаваемый программой, как бы намекает на присутствие в ней ошибки.
function numberToString(n, base) {
var result = «», sign = «»;
result = String(n % base) + result;
console.log(numberToString(13, 10));
// → 1.5e-3231.3e-3221.3e-3211.3e-3201.3e-3191.3e-3181.3…
Даже если вы нашли проблему – притворитесь, что ещё не нашли. Мы знаем, что программа сбоит, и нам нужно узнать, почему.
Здесь вам надо преодолеть желание начать вносить случайные изменения в код. Вместо этого подумайте. Проанализируйте результат и придумайте теорию, по которой это происходит. Проведите дополнительные наблюдения для проверки теории, а если теории нет – проведите наблюдения, которые бы помогли вам изобрести её.
Размещение нескольких вызовов console.log в стратегических местах – хороший способ получить дополнительную информацию о том, что программа делает. В нашем случае нам нужно, чтобы n принимала значения 13, 1, затем 0. Давайте выведем значения в начале цикла:
Н-да. Деление 13 на 10 выдаёт не целое число. Вместо n /= base нам нужно n = Math.floor(n / base), тогда число будет корректно «сдвинуто» вправо.
Кроме console.log можно воспользоваться отладчиком в браузере. Современные браузеры умеют ставить точку остановки на выбранной строчке кода. Это приведёт к приостановке выполнения программы каждый раз, когда будет достигнута выбранная строчка, и тогда вы сможете просмотреть содержимое переменных. Не буду подробно расписывать процесс, поскольку у разных браузеров он организован по-разному – поищите в вашем браузере “developer tools”, инструменты разработчика. Ещё один способ установить точку остановки – включить в код инструкцию для отладчика, состоящую из ключевого слова debugger. Если инструменты разработчика активны, исполнение программы будет приостановлено на этой инструкции, и вы сможете изучить состояние программы.
К сожалению, программист может предотвратить появление не всех проблем. Если ваша программа общается с внешним миром, она может получить неправильные входные данные, или же системы, с которыми она пытается взаимодействовать, окажутся сломанными или недоступными.
Простые программы, или программы, работающие под вашим надзором, могут просто «сдаваться» в такой момент. Вы можете изучить проблему и попробовать снова. «Настоящие» приложения не должны просто «падать». Иногда приходится принимать неправильные входные данные и как-то с ними работать. В других случаях нужно сообщить пользователю, что что-то пошло не так, и потом уже сдаваться. В любом случае программа должна что-то сделать в ответ на возникновение проблемы.
Допустим, у вас есть функция promptInteger, которая запрашивает целое число и возвращает его. Что она должна сделать, если пользователь введёт «апельсин»?
Один из вариантов – вернуть особое значение. Обычно для этих целей используют null и undefined.
function promptNumber(question) {
var result = Number(prompt(question, «»));
if (isNaN(result)) return null;
console.log(promptNumber(«Сколько пальцев видите?»));
Это надёжная стратегия. Теперь любой код, вызывающий promptNumber, должен проверять, было ли возвращено число, и если нет, как-то выйти из ситуации – спросить снова, или задать значение по-умолчанию. Или вернуть специальное значение уже тому, кто его вызвал, сообщая о неудаче.
Во многих таких случаях, когда ошибки возникают часто и вызывающий функцию код должен принимать их во внимание, совершенно допустимо возвращать специальное значение как индикатор ошибки. Но есть и минусы. Во-первых, что, если функция и так может вернуть любой тип значения? Для неё сложно найти специальное значение, которое будет отличаться от допустимого результата.
Вторая проблема – работа со специальными значениями может замусорить код. Если функция promptNumber вызывается 10 раз, то надо 10 раз проверить, не вернула ли она null. Если реакция на null заключается в возврате null на уровень выше, тогда там, где вызывался этот код, тоже нужно встраивать проверку на null, и так далее.
Когда функция не может работать нормально, мы бы хотели остановить работу и перепрыгнуть туда, где такая ошибка может быть обработана. Этим занимается обработка исключений.
Код, встретивший проблему в момент выполнения, может поднять (или выкинуть) исключение (raise exception, throw exception), которое представляет из себя некое значение. Возврат исключения напоминает некий «прокачанный» возврат из функции – он выпрыгивает не только из самой функции, но и из всех вызывавших её функций, до того места, с которого началось выполнение. Это называется развёртыванием стека (unwinding the stack). Может быть, вы помните стек функций из главы 3… Исключение быстро проматывает стек вниз, выкидывая все контексты вызовов, которые встречает.
Если бы исключения сразу доходили до самого низа стека, пользы от них было бы немного. Они бы просто предоставляли интересный способ взорвать программу. Их сила в том, что на их пути в стеке можно поставить «препятствия», которые будут ловить исключения, мчащиеся по стеку. И тогда с этим можно сделать что-то полезное, после чего программа продолжает выполняться с той точки, где было поймано исключение.
function promptDirection(question) {
var result = prompt(question, «»);
if (result.toLowerCase() == «left») return «L»;
if (result.toLowerCase() == «right») return «R»;
throw new Error(«Недопустимое направление: « + result);
if (promptDirection(«Куда?») == «L»)
return «двух разъярённых медведей»;
console.log(«Вы видите», look());
console.log(«Что-то не так: « + error);
Ключевое слово throw используется для выбрасывания исключения. Ловлей занимается кусок кода, обёрнутый в блок try, за которым следует catch. Когда код в блоке try выкидывает исключение, выполняется блок catch. Переменная, указанная в скобках, будет привязана к значению исключения. После завершения выполнения блока catch, или же если блок try выполняется без проблем, выполнение переходит к коду, лежащему после инструкции try/catch.
В данном случае для создания исключения мы использовали конструктор Error. Это стандартный конструктор, создающий объект со свойством message. В современных окружениях JavaScript экземпляры этого конструктора также собирают информацию о стеке вызовов, который был накоплен в момент выкидывания исключения – так называемое отслеживание стека (stack trace). Эта информация сохраняется в свойстве stack, и может помочь при разборе проблемы – она сообщает, в какой функции случилась проблема и какие другие функции привели к данному вызову.
Обратите внимание, что функция look полностью игнорирует возможность возникновения проблем в promptDirection. Это преимущество исключений – код, обрабатывающий ошибки, нужен только в том месте, где происходит ошибка, и там, где она обрабатывается. Промежуточные функции просто не обращают на это внимания.
Подчищаем за исключениями
Подчищаем за исключениями
Представьте следующую ситуацию: функция withContext желает удостовериться, что во время её выполнения переменная верхнего уровня context содержит специальное значение контекста. В конце выполнения функция восстанавливает прежнее значение переменной.
function withContext(newContext, body) {
var oldContext = context;
Что, если функция body выбросит исключение? В таком случае вызов withContext будет выброшен исключением из стека, и переменной context никогда не будет возвращено первоначальное значение.
Но у инструкции try есть ещё одна особенность. За ней может следовать блок finally, либо вместо catch, либо вместе с catch. Блок finally означает «выполнить код в любом случае после выполнения блока try». Если функции надо что-то подчистить, то подчищающий код нужно включать в блок finally.
function withContext(newContext, body) {
var oldContext = context;
Заметьте, что нам больше не нужно сохранять результат вызова body в отдельной переменной, чтобы вернуть его. Даже если мы возвращаемся из блока try, блок finally всё равно будет выполнен. Теперь мы можем безопасно сделать так:
withContext(5, function() {
throw new Error(«Контекст слишком мал!»);
console.log(«Игнорируем: « + e);
// → Игнорируем: Error: Контекст слишком мал!
Несмотря на то, что вызываемая из withContext функция «сломалась», сам по себе withContext по-прежнему подчищает значение переменной context.
Выборочный отлов исключений
Выборочный отлов исключений
Когда исключение доходит до низа стека и его никто не поймал, его обрабатывает окружение. Как именно – зависит от конкретного окружения. В браузерах описание ошибки выдаётся в консоль (она обычно доступна в меню «Инструменты» или «Разработка»).
Если речь идёт об ошибках или проблемах, которые программа не может обработать в принципе, допустимо просто пропустить такую ошибку. Необработанное исключение – разумный способ сообщить о проблеме в программе, и консоль в современных браузерах выдаст вам необходимую информацию о том, какие вызовы функций были в стеке в момент возникновения проблемы.
Если возникновение проблемы предсказуемо, программа не должна падать с необработанным исключением – это не очень дружественно по отношению к пользователю.
Недопустимое использование языка – ссылки на несуществующую переменную, запрос свойств у переменной, равной null, или вызов чего-то, что не является функцией – тоже приводит к выбрасыванию исключений. Такие исключения можно отлавливать точно так же, как свои собственные.
При входе в блок catch мы знаем только, что что-то внутри блока try привело к исключению. Мы не знаем, что именно, и какое исключение произошло.
JavaScript (что является вопиющим упущением) не предоставляет непосредственной поддержки выборочного отлова исключений: либо ловим все, либо никакие. Из-за этого люди часто предполагают, что случившееся исключение – именно то, ради которого и писался блок catch.
Но может быть и по-другому. Нарушение произошло где-то ещё, или в программу вкралась ошибка. Вот пример, где мы пробуем вызывать promptDirection до тех пор, пока не получим допустимый ответ:
var dir = promtDirection(«Куда?»); // ← опечатка!
console.log(«Ваш выбор», dir);
console.log(«Недопустимое направление. Попробуйте ещё раз.»);
Конструкция for (;;) – способ устроить бесконечный цикл. Мы вываливаемся из него, только когда получаем допустимое направление. Но мы неправильно написали название promptDirection, что приводит к ошибке “undefined variable”. А так как блок catch игнорирует значение исключения e, предполагая, что он разбирается с другой проблемой, он считает, что выброшенное исключение является результатом неправильных входных данных. Это приводит к бесконечному циклу и скрывает полезное сообщение об ошибке насчёт неправильного имени переменной.
Как правило, не стоит так ловить исключения, если только у вас нет цели перенаправить их куда-либо – к примеру, по сети, чтобы сообщить другой системе о падении нашей программы. И даже тогда внимательно смотрите, не будет ли скрыта важная информация.
Значит, нам надо поймать определённое исключение. Мы можем в блоке catch проверять, является ли случившееся исключение интересующим нас исключением, а в противном случае заново выбрасывать его. Но как нам распознать исключение?
Конечно, мы могли бы сравнить свойство message с сообщением об ошибке, которую мы ждём. Но это ненадёжный способ писать код – использовать информацию, предназначающуюся для человека (сообщение), чтобы принять программное решение. Как только кто-нибудь поменяет или переведёт это сообщение, код перестанет работать.
Давайте лучше определим новый тип ошибки и используем instanceof для его распознавания.
function InputError(message) {
this.stack = (new Error()).stack;
InputError.prototype = Object.create(Error.prototype);
InputError.prototype.name = «InputError»;
Прототип наследуется от Error.prototype, поэтому instanceof Error тоже будет выполняться для объектов типа InputError. И ему назначено свойство name, как и другим стандартным типам ошибок (Error, SyntaxError, ReferenceError, и т.п.)
Присвоение свойству stack пытается передать этому объекту отслеживание стека, на тех платформах, которые это поддерживают, путём создания объекта Error и использования его стека.
Теперь promptDirection может сотворить такую ошибку.
function promptDirection(question) {
var result = prompt(question, «»);
if (result.toLowerCase() == «left») return «L»;
if (result.toLowerCase() == «right») return «R»;
throw new InputError(«Invalid direction: « + result);
А в цикле её будет ловить сподручнее.
var dir = promptDirection(«Куда?»);
console.log(«Ваш выбор», dir);
if (e instanceof InputError)
console.log(«Недопустимое направление. Попробуйте ещё раз.»);
Код отлавливает только экземпляры InputError и пропускает другие исключения. Если вы снова сделаете такую же опечатку, будет корректно выведено сообщение о неопределённой переменной.
Утверждения – инструмент для простой проверки ошибок. Рассмотрим вспомогательную функцию assert:
function AssertionFailed(message) {
AssertionFailed.prototype = Object.create(Error.prototype);
function assert(test, message) {
throw new AssertionFailed(message);
function lastElement(array) {
assert(array.length > 0, «пустой массив в lastElement»);
return array[array.length — 1];
Это – компактный способ ужесточения требований к значениям, который выбрасывает исключение в случае, когда заданное условие не выполняется. К примеру, функция lastElement, добывающая последний элемент массива, вернула бы undefined для пустых массивов, если бы мы не использовали assertion. Извлечение последнего элемента пустого массива не имеет смысла, и это явно было бы ошибкой программиста.
Утверждения – способ убедиться в том, что ошибки провоцируют прерывание программы в том месте, где они совершены, а не просто выдают странные величины, которые передаются по системе и вызывают проблемы в каких-то других, не связанных с этим, местах.
Ошибки и недопустимые входные данные случаются в жизни. Ошибки в программах надо искать и исправлять. Их легче найти, используя автоматические системы проверок и добавляя утверждения в ваши программы.
Проблемы, вызванные чем-то, что неподвластно вашей программе, нужно обрабатывать достойно. Иногда, когда проблему можно решить локально, допустимо возвращать специальные значения для отслеживания таких случаев. В других случаях предпочтительно использовать исключения.
Выброс исключения приводит к разматыванию стека до тех пор, пока не будет встречен блок try/catch или пока мы не дойдём до дна стека. Значение исключения будет передано в блок catch, который сможет удостовериться в том, что это исключение действительно то, которое он ждёт, и обработать его. Для работы с непредсказуемыми событиями в потоке программы можно использовать блоки finally, чтобы определённые части кода были выполнены в любом случае.
Допустим, у вас есть функция primitiveMultiply, которая в 50% случаев перемножает 2 числа, а в остальных случаях выбрасывает исключение типа MultiplicatorUnitFailure. Напишите функцию, обёртывающую эту, и просто вызывающую её до тех пор, пока не будет получен успешный результат.
Убедитесь, что вы обрабатываете только нужные вам исключения.
function MultiplicatorUnitFailure() {}
function primitiveMultiply(a, b) {
throw new MultiplicatorUnitFailure();
function reliableMultiply(a, b) {
console.log(reliableMultiply(8, 8));
Рассмотрим такой, достаточно надуманный, объект:
unlock: function() { this.locked = false; },
lock: function() { this.locked = true; },
if (this.locked) throw new Error(«Заперто!»);
Это коробочка с замком. Внутри лежит массив, но до него можно добраться только, когда коробка не заперта. Напрямую обращаться к свойству _content нельзя.
Напишите функцию withBoxUnlocked, принимающую в качестве аргумента функцию, которая отпирает коробку, выполняет функцию, и затем обязательно запирает коробку снова перед выходом – неважно, выполнилась ли переданная функция правильно, или она выбросила исключение.
function withBoxUnlocked(body) {
withBoxUnlocked(function() {
box.content.push(«золотишко»);
withBoxUnlocked(function() {
throw new Error(«Пираты на горизонте! Отмена!»);
console.log(«Произошла ошибка:», e);
В качестве призовой игры убедитесь, что при вызове withBoxUnlocked, когда коробка не заперта, коробка остаётся незапертой.