- PVSM.RU - https://www.pvsm.ru -
Обработка ошибок в JS – та еще головная боль. Не ошибусь, если скажу, что ошибки – самое слабое место всего языка. При чем проблема эта составная и состоит она из двух других проблем: сложность отлова ошибки в асинхронном коде и плохо спроектированный объект Error. Вторая, на самом, деле менее очевидная, поэтому с нее и начнем.
Реализация объекта ошибки в JS – одна из самых ужасных, которые я когда либо встречал. Мало того сама реализация отличается в различных движках. Объект спроектирован (и развивается) так будто ни до, ни после возникновения JS с ошибками вообще не работали. Я даже не знаю с чего начать. Этот объект не является программно-интерпретируемым, так как все важные значения являются склеенными строками. Отсутствует механизм захвата стека вызовов и алгоритм расширения ошибки.
Результатом этого является то, что каждый разработчик вынужден самостоятельно принимать решение в каждом отдельном случае, но, как доказали ученые, выбор вызывает у людей дискомфорт, поэтому очень часто ошибки просто-напросто игнорируются и попадают в основной поток. Так же, достаточно часто, вместо ошибки вы можете получить Array или Object, спроектированные "на свое усмотрение". Поэтому вместо единой системы обработки ошибок мы сталкиваемся с набором уникальных правил для каждого отдельного случая.
И это не только мои слова, тот же TJ Holowaychuck написал об этом в своем письме прощаясь с сообществом nodejs.
Как же решить проблему? Создать единую стратегию формирования и обработки сообщения об ошибке! Разработчики Google предлагают пользователям V8 собственный набор инструментов, которые облегчают эту задачу. И так приступим.
Давайте начнем с создания собственного объекта ошибки. В классической теории, все что вы можете сделать – создать экземпляр Error, а затем дополнить его, вот как это выглядит:
var error = new Error('Some error');
error.name = 'My Error';
error.customProperty = 'some value';
throw error;
И так для каждого случая? Да! Конечно, можно было бы создать конструктор MyError и в нем установить нужные значения полей:
function MyError(message, customProperty) {
Error.call(this);
this.message = message;
this.customProperty = customProperty;
}
Но так мы получим в стеке лишнюю запись об ошибке, что усложнит поиск ошибки другим разработчикам. Решением является метод Error.captureStackTrace. Он получает на вход два значения: объект, в который будет записан стек и функция-конструктор, запись о которой из стека нужно изъять.
function MyError(message, customProperty) {
Error.captureStackTrace(this, this.constructor);
this.message = message;
this.customProperty = customProperty;
}
// Для успешного сравнения с помощью ...instanceof Error:
var inherits = require('util').inherits;
inherits(MyError, Error);
Теперь где бы не всплыла ошибка в стеке на первом месте будет стоять адрес вызова new Error.
Следующим пунктом в решении проблемы стоит идентификация ошибки. Для того чтобы программно ее обработать и принять решение о дальнейших действиях: выдать пользователю сообщение или завершить работу. Поле message не дает таких возможностей: парсить сообщение регулярным выражением не представляется разумным. Как же тогда отличить ошибку неверного параметра от ошибки соединения? В самом nodejs для этого используется поле code. При этом в стандарте для классификации ошибок предписывается использовать поле name [1]. Но используются они по разному, поэтому рекомендую использовать для этого следующие правила:
MyError
.SOMETHING_WRONG
.ERROR
.ConnectionError
либо MongoError
, вместо MongoConnectionError
.Пример:
Чтобы создать отчет об ошибке чтения файла по причине того что файл отсутствует можно указать следующие значения: FileSystemError
для name
и FILE_NOT_FOUND
для code
, а также к ошибке следует добавить поле file
.
Так же в V8 есть функция Error.prepareStackTrace
для получения сырого стека – массива CallSite объектов. CallSite – это объекты, которые содержат информацию о вызове: адрес ошибки (метод, файл, строка) и ссылки непосредственно на сами объекты чьи методы были вызваны. Таким образом в наших руках оказывается достаточно мощный и гибкий инструмент для дебага приложений.
Для того чтобы получить стек необходимо создать функцию, которая на вход получает два аргумента: непосредственно ошибка и массив CallSite объектов, вернуть необходимо готовую строку. Эта функция будет вызываться для каждой ошибок, при обращении к полю stack. Созданную функцию необходимо добавить в сам Error как prepareStackTrace:
Error.prepareStackTrace = function(error, stack) {
// ...
return error + ':n' + stackAsString;
};
Давайте подробнее рассмотрим объект CallSite содержащийся в массиве stack. Он имеет следующие методы:
getThis | возвращает значение this. |
getTypeName | возвращает тип this в виде строки, обычно это поле name конструктора. |
getFunction | возвращает функцию. |
getFunctionName | возвращает имя функции, обычно это значение поля name. |
getMethodName | возвращает имя поля объекта this. |
getFileName | возвращает имя файла (или скрипта для браузера). |
getLineNumber | возвращает номер строки. |
getColumnNumber | возвращает смещение в строке. |
getEvalOrigin | возвращает место вызова eval, если функция была объявлена внутри вызова eval. |
isTopLevel | является ли вызов вызовом из глобальной области видимости. |
isEval | является ли вызов вызовом из eval. |
isNative | является ли вызваный метод внутренним. |
isConstructor | является ли метод вызовом конструктора. |
Как я уже говорил выше этот метод будет вызываться один раз и для каждой ошибки. При этом вызов будет происходить только при обращении к полю stack. Как это использовать? Можно внутри метода добавить к ошибке стек в виде массива:
Error.prepareStackTrace = function(error, stack) {
error._stackAsArray = stack.map(function(call){
return {
// ...
file : call.getFileName()
};
});
// ...
return error + ':n' + stackAsString;
};
А затем в саму ошибку добавить динамическое свойство для получение стека.
Object.defineProperty(MyError.prototype, 'stackAsArray', {
get : function() {
// Инициируем вызов prepareStackTrace
this.stack;
return this._stackAsArray;
}
});
Так мы получили полноценный отчет, который доступен программно и позволяет отделить системные вызовы от вызовов модулей и от вызовов самого приложения для подробного анализа и обработки. Сразу оговорюсь, что тонкостей и вопросов при анализе стека может возникнуть очень много, поэтому, если хотите разобраться, советую покопаться самостоятельно.
Все изменения в API следует отслеживать на wiki-странице [2] v8 посвященной ErrorTraceAPI.
На этом хочу закончить, наверное для вводной статьи этого достаточно. Ну и надеюсь, что кому-то этот материал спасет время и нервы в будущем.
В следующей статье я расскажу, как сделать работу с ошибками комфортной с помощью описаных в статье подходов и инструментов.
Автор: rumkin
Источник [3]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/node-js/75876
Ссылки в тексте:
[1] предписывается использовать поле name: http://www.ecma-international.org/ecma-262/5.1/#sec-15.11.7
[2] wiki-странице: https://code.google.com/p/v8-wiki/wiki/JavaScriptStackTraceApi
[3] Источник: http://habrahabr.ru/post/244523/
Нажмите здесь для печати.