Partilhar via


Более быстрая диагностика ошибок в коде JavaScript с помощью Error.stack

Internet Explorer 10 в Windows 8 Consumer Preview обеспечивает поддержку Error.stack. Благодаря этому веб-разработчики могут быстрее диагностировать и исправлять ошибки, особенно те, которые трудно воспроизвести. Разработчики могут создавать замечательные приложения, используя возможности веб-платформ, лежащих в основе современных браузеров. В Windows 8 эти эффективные возможности представлены через Internet Explorer 10 и приложения в стиле Metro на JavaScript. Рост эффективности и сложности этих приложений приводит к тому, что разработчикам нужны более удобные средства диагностики и обработки ошибок, такие как Error.stack.

Отладка приложений

Структурированная обработка ошибок в JavaScript базируется на throw и try/catch, где разработчик объявляет ошибку и передает поток управления в раздел программы, связанный с обработкой ошибок. Когда генерируется ошибка, Chakra — обработчик JavaScript в Internet Explorer — захватывает цепочку вызовов, которая привела к источнику ошибки. Эту цепочку вызовов также называют стеком вызовов. Если генерируется объект Error (или функция, цепочка прототипов которой ведет обратно к Error), Chakra создает трассировку стека — понятное для человека описание стека вызовов. Это описание представлено в виде свойства stack объекта Error. Свойство stack содержит сообщение об ошибке, имена функций и сведения о местоположении исходных файлов функций. С помощью этих данных разработчики могут быстро выявлять дефекты, узнав, какая функция вызывалась, и даже увидеть, какая строка кода привела к ошибке. Например, можно выяснить, что переданный в функцию параметр имел значение Null или был недействительным.

Давайте рассмотрим пример простого скрипта для расчета расстояния между двумя точками — (0, 2) и (12, 10):

(function () {

'use strict';

function squareRoot(n) {

if (n < 0)

throw new Error('Cannot take square root of negative number.');

 

return Math.sqrt(n);

}

 

function square(n) {

return n * n;

}

 

function pointDistance(pt1, pt2) {

return squareRoot((pt1.x - pt2.x) + (pt1.y - pt2.y));

}

 

function sample() {

var pt1 = { x: 0, y: 2 };

var pt2 = { x: 12, y: 10 };

 

console.log('Distance is: ' + pointDistance(pt1, pt2));

}

 

try {

sample();

}

catch (e) {

console.log(e.stack);

}

})();

Этот скрипт содержит ошибку — в нем не возводится в квадрат разность координат. Это приводит к тому, что для некоторых входных данных функция pointDistance будет возвращать неправильные результаты, а в других случаях возникнет ошибка. Чтобы понять, что такое трассировка стека, давайте изучим данную ошибку с помощью средств разработчика F12 и посмотрим на вкладку Script:

Снимок экрана средств разработчика F12, на котором представлена трассировка стека, записанная в журнал посредством вызова console.log(e.stack), где e — объект Error, переданный в предложение catch блока try/catch.

Трассировка стека выводится на консоль в предложении catch, и сразу становится понятно, что ошибка возникает в функции squareRoot, поскольку она находится в верхней части стека. Чтобы устранить эту проблему, разработчику не потребуется слишком углубляться в чтение трассировки стека. Было нарушено необходимое условие для функции squareRoot, и, если посмотреть в стеке на один уровень выше, становится ясно, почему это произошло: части выражения в вызове функции squareRoot сами должны быть параметрами для square.

Свойство stack можно использовать во время отладки, чтобы определить, где в коде следует поставить точку останова. Помните о том, что есть и другие способы просмотра стека вызовов. Например, если задать для отладчика скриптов режим останова при перехвате исключения, можно проверять стек вызовов с помощью отладчика. Для развернутых приложений можно разместить проблемный код в блоке try/catch, чтобы захватывать ошибки вызовов и записывать их в журнал на сервере. Тогда разработчики смогут использовать стек вызовов для выявления проблемных областей.

Исключения DOM и Error.stack

Как я уже отметил ранее, генерируемый объект должен быть Error или вести к Error через цепочку прототипов. Это сделано преднамеренно. В JavaScript любой объект и даже примитивы могут генерироваться как исключения. Хотя все они могут быть захвачены и проверены, не все из них специально разрабатывались с возможностью содержать ошибку или диагностические данные. Поэтому только объекты Error будут при генерации обновляться с использованием свойства stack.

Несмотря на то, что исключения DOM являются объектами, они не имеют цепочек прототипов, ведущих к Error, и, следовательно, у них нет свойства stack. В сценариях, в которых выполняется работа с DOM и нужно выявить совместимые с JavaScript ошибки, может потребоваться разместить код работы с DOM в блоке try/catch и сгенерировать новый объект Error в предложении catch:

function causesDomError() {

try {

var div = document.createElement('div');

div.appendChild(div);

} catch (e) {

throw new Error(e.toString());

}

}

Вы, вероятно, задумаетесь, стоит ли использовать такую модель. Она лучше всего подходит для разработки библиотек служебных программ. Нужно решить, требуется ли в вашем коде скрыть работу с DOM или просто выполнить задачу. Если нужно скрыть работу с DOM, размещение соответствующих операций в блоке try/catch и генерация объекта Error может оказаться правильным подходом.

Вопросы производительности

Формирование трассировки стека начинается в момент генерации объекта Error; для этого требуется прохождение текущего стека выполнения. Чтобы избежать проблем с производительностью при прохождении особенно большого стека (возможно, даже рекурсивной цепочки стеков), Internet Explorer собирает по умолчанию только 10 верхних кадров стека. Однако этот параметр можно настроить, задав для статического свойства Error.stackTraceLimit другое значение. Это глобальный параметр, и он должен быть изменен до генерации ошибки, в противном случае он не повлияет на трассировки стека.

Асинхронные исключения

Когда трассировка стека формируется из асинхронного обратного вызова (например, timeout, interval или XMLHttpRequest), внизу стека вызовов будет, скорее всего, асинхронный обратный вызов, а не код, создавший этот асинхронный обратный вызов. Это имеет некоторое потенциальное влияние на отслеживание неправильного кода: если одна и та же функция обратного вызова используется для нескольких асинхронных обратных вызовов, может быть сложно определить только на основе просмотра, какой обратный вызов стал причиной ошибки. Давайте немного изменим наш предыдущий пример и вместо прямого вызова функции sample() поместим ее в обратный вызов timeout:

(function () {

'use strict';

function squareRoot(n) {

if (n < 0)

throw new Error('Cannot take square root of negative number.');

 

return Math.sqrt(n);

}

 

function square(n) {

return n * n;

}

 

function pointDistance(pt1, pt2) {

return squareRoot((pt1.x - pt2.x) + (pt1.y - pt2.y));

}

 

function sample() {

var pt1 = { x: 0, y: 2 };

var pt2 = { x: 12, y: 10 };

 

console.log('Distance is: ' + pointDistance(pt1, pt2));

}

 

setTimeout(function () {

try {

sample();

}

catch (e) {

console.log(e.stack);

}

}, 2500);

})();

При выполнении этого фрагмента кода вы увидите, что трассировка стека отобразилась после небольшой задержки. В этот раз вы также увидите, что внизу стека находится не Global Code, а Anonymous function. Фактически это не та же самая анонимная функция, а функция обратного вызова, переданная в setTimeout. Поскольку теряется контекст, окружающий обработку обратного вызова, вы, возможно, не сможете определить, что именно стало причиной выполнения обратного вызова. Если вы рассмотрите сценарий, в котором один обратный вызов регистрируется для обработки события click нескольких разных кнопок, вы не сможете отличить, к какому обратному вызову относится зарегистрированный вызов. Следует сказать, что это незначительное ограничение, поскольку в большинстве случаев проблемные области будут обозначены вверху стека.

Тестовая интерактивная демонстрация

Снимок экрана тестовой интерактивной демонстрации "Explore Error.stack" (Изучение Error.stack)

Ознакомьтесь с этой тестовой интерактивной демонстрацией, используя Internet Explorer 10 в Windows 8 Consumer Preview. Вы можете выполнить код в контексте eval и изучить ошибку, если она возникнет. При выполнении кода в Internet Explorer 10 вы также сможете выделять строки кода, наводя указатель на строки ошибок в трассировке стека. Вы можете сами ввести код в области кода или выбрать один из нескольких примеров в списке. Кроме того, при выполнении примеров кода можно задать значение Error.stackTraceLimit.

Справочные материалы можно найти в документации MSDN для Error.stack и stackTraceLimit.

— Роб Павеза (Rob Paveza), руководитель программы, среда выполнения Chakra