Some error name

Обработка ошибок в JS – та еще головная боль. Не ошибусь, если скажу, что ошибки – самое слабое место всего языка. При чем проблема состоит из двух других: сложн...

Обработка ошибок в JS – та еще головная боль. Не ошибусь, если скажу, что ошибки – самое слабое место всего языка. При чем проблема состоит из двух других: сложности отлова ошибки в асинхронном коде и плохо спроектированного объекта Error. И если первой проблеме посвящено множество статей, то о второй многие незаслуженно забывают. В этом материале я постараюсь восполнить недостаток и рассмотреть объект Error более пристально.

Корень зла

Реализация объекта ошибки в JS – одна из самых ужасных, которые я когда либо встречал. Мало того сама реализация отличается в различных движках. Объект спроектирован (и развивается) так будто ни до, ни после возникновения JS с ошибками вообще не работали. Я даже не знаю с чего начать. Этот объект не является программно-интерпретируемым, так как все важные значения являются склеенными строками. Отсутствует механизм захвата стека вызовов и алгоритм расширения ошибки.

Результатом этого является то, что каждый разработчик вынужден самостоятельно принимать решение в каждом отдельном случае, но, как доказали ученые, выбор вызывает у людей дискомфорт, поэтому очень часто ошибки просто-напросто игнорируются и попадают в основной поток. Так же, достаточно часто, вместо ошибки вы можете получить Array или Object, спроектированные «на свое усмотрение». Поэтому вместо единой системы обработки ошибок мы сталкиваемся с набором уникальных правил для каждого отдельного случая.
И это не только мои слова, тот же TJ Holowaychuck написал об этом в своем письме прощаясь с сообществом nodejs.

Как же решить проблему? Создать единую стратегию формирования и обработки сообщения об ошибке! Разработчики Google предлагают пользователям V8 собственный набор инструментов, которые облегчают эту задачу. И так приступим.

MyError

Давайте начнем с создания собственного объекта ошибки. В классической теории, все что вы можете сделать – создать экземпляр 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, name и code

Следующим пунктом в решении проблемы стоит идентификация ошибки. Для того чтобы программно ее обработать и принять решение о дальнейших действиях: выдать пользователю сообщение или завершить работу. Поле message не дает таких возможностей: парсить сообщение регулярным выражением не представляется разумным. Как же тогда отличить ошибку неверного параметра от ошибки соединения? В самом nodejs для этого используется поле code. При этом в стандарте для классификации ошибок предписывается использовать поле name. Но используются они по разному, поэтому рекомендую использовать для этого следующие правила:

  1. Поле name должно содержать значение в «скачущем» регистре: MyError.
  2. Поле code должно содержать значение разделенное подчеркиванием, символы должны быть в верхнем регистре: SOMETHING_WRONG.
  3. Не используйте в поле code слово ERROR.
  4. Значение в name создано для классификация ошибок, поэтому лучше использовать ConnectionError либо MongoError, вместо MongoConnectionError.
  5. Значение code должно быть уникальным.
  6. Поле message должно формироваться на основе значения code и переданных переменных параметров.
  7. Для успешной обработки ошибки желательно добавить дополнительные сведения в сам объект.
  8. Дополнительные значения должны быть примитивами: не стоит передавать в объект ошибки соединение с базой данных.

Пример:

Чтобы создать отчет об ошибке чтения файла по причине того что файл отсутствует можно указать следующие значения: 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-странице v8 посвященной ErrorTraceAPI.

Заключение

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

UPD. Всем кто ждет откровений про отлов асинхронных ошибок: to be continued…



Оператор try позволяет вам проверить блок кода на наличие ошибок.

Оператор catch позволяет вам обработать ошибку.

Оператор throw позволяет создавать собственные ошибки.

Оператор finally позволяет выполнить код, после того, как попытаться поймать, независимо от результата.


Ошибки будут!

При выполнении кода JavaScript могут возникать разные ошибки.

Ошибки могут быть ошибками кодирования, допущенны программистом, ошибками из-за неправильного ввода или другими непредвиденными ситуациями.

Пример

В этом примере мы в место alert написали adddlert, чтобы намеренно выдать ошибку:

<p id=»demo»></p>

<script>
try {
  adddlert(«Добро пожаловать!»);
}
catch(err) {
 
document.getElementById(«demo»).innerHTML = err.message;
}
</script>

Попробуйте сами »

JavaScript перехватывает adddlert, как ошибку и выполняет код перехвата для ее обработки.


JavaScript try и catch

Оператор try, позволяет обнаружить блок кода, который будет проверяться на наличие ошибок во время его выполнения.

Оператор catch, позволяет поймать блок кода, который будет выполняться, если в блоке try возникает ошибка.

Оператор JavaScript try и catch идут в парах:

try {
  Блок кода для проверки
}
catch(err) {
  Блок кода для обработки ошибок
}



JavaScript выдает ошибки

Когда происходит ошибка, JavaScript обычно останавливается и генерирует сообщение об ошибке.

Технический термин для этого: JavaScript вызовет исключение (выдаст ошибку).

JavaScript фактически создаст объект Error с двумя свойствами: name и message.


Оператор throw

Оператор throw позволяет создать пользовательскую ошибку.

Технически вы можете сгенерировать исключение (сгенерировать ошибку).

Исключением может быть JavaScript String, Number, Boolean или Object:

throw «Слишком большой»;    // пропустить текст
throw 500;          // пропустить число

Если вы используете throw вместе с try и catch, вы можете контролировать выполнение программы и генерировать собственные сообщения об ошибках.


Пример проверки ввода

В этом примере исследуется ввод. Если значение неверно, генерируется исключение (ошибка).

Исключение (err) перехватывается оператором catch, и отображается настраиваемое сообщение об ошибке:

<!DOCTYPE html>
<html>
<body>

<p>Пожалуйста, введите число от 5 до 10:</p>

<input id=»demo» type=»text»>
<button type=»button»
onclick=»myFunction()»>Ввод текста</button>
<p id=»p01″></p>

<script>
function myFunction() {
  var message, x;
  message =
document.getElementById(«p01»);
  message.innerHTML = «»;
  x =
document.getElementById(«demo»).value;
 
try {
    if(x == «») throw «пусто»;
   
if(isNaN(x)) throw «не число»;
   
x = Number(x);
    if(x < 5) throw
«слишком маленькое»;
    if(x > 10) throw «слишком
большое»;
  }
  catch(err) {
    message.innerHTML =
«Вывод » + err;
  }
}
</script>

</body>
</html>

Попробуйте сами »


Проверка HTML на валидность

Код выше — является просто примером.

Современные браузеры часто используют комбинацию JavaScript и встроенной проверки HTML, используя предопределенные правила проверки, определенные в атрибутах HTML:

<input id=»demo» type=»number» min=»5″ max=»10″ step=»1″>

Вы можете узнать больше о проверке форм в следующей главе этого руководства.


Оператор finally

Оператор finally позволяет выполнить код, после try и catch, еще раз попытаться поймать, независимо от результата:

Синтаксис

try {
  блок кода для попытки обнаружить ошибки
}
catch(err) {
  блок кода для обработки поймать ошибки
}

finally {
  блок кода, который будет выполняться независимо от результата try/catch
}

Пример

function myFunction() {
  var message, x;
  message =
document.getElementById(«p01»);
  message.innerHTML = «»;
  x =
document.getElementById(«demo»).value;
 
try {
   
if(x == «») throw «пусто»;
    if(isNaN(x))
throw «не число»;
   
x = Number(x);
    if(x >
10) throw «слишком большое»;
    if(x <
5) throw «слишком маленькое»;
  }
  catch(err)
{
    message.innerHTML = «Ошибка: » +
err + «.»;
  }
  finally {
    document.getElementById(«demo»).value = «»;
  }
}

Попробуйте сами »


Объект Error

JavaScript имеет встроенный объект Error, который предоставляет информацию об ошибке при возникновении ошибки.

Объект Error предоставляет два полезных свойства: имя и сообщение.


Свойства объекта Error

Свойство Описание
имя Задает или возвращает имя ошибки
сообщение Устанавливает или возвращает сообщение об ошибке (строку)

Значения Error Name

Шесть различных значений могут быть возвращены свойством Error Name:

Error Name Описание
EvalError Произошла ошибка в функции eval()
RangeError Произошло число «вне допустимого диапазона»
ReferenceError Произошла недопустимая ссылка
SyntaxError Произошла синтаксическая ошибка
TypeError Произошла ошибка типа
URIError Произошла ошибка в encodeURI()

Ниже описаны шесть различных значений.


Ошибка EvalError

Один EvalError указывает на ошибку в функции eval().

Новые версии JavaScript не генерируют EvalError. Вместо этого используйте SyntaxError.


Ошибка RangeError

Ошибка RangeError генерируется, если вы используете число, выходящее за пределы диапазона допустимых значений.

Например: Вы не можете установить количество значащих цифр числа на 500.

Пример

var num = 1;
try {
  num.toPrecision(500);   // Число не может содержать 500 значащих цифр
}
catch(err) {
  document.getElementById(«demo»).innerHTML = err.name;
}

Попробуйте сами »


Ошибка ReferenceError

Ошибка ReferenceError генерируется, если вы используете (ссылаетесь на неё) переменную,
которая не была объявлена:

Пример

var x;
try {
  x = y + 1;   // на y нельзя ссылаться (использовать)
}
catch(err) {
  document.getElementById(«demo»).innerHTML = err.name;
}

Попробуйте сами »


Ошибка SyntaxError

Ошибка SyntaxError генерируется, если вы пытаетесь запустить код с синтаксической ошибкой.

Пример

try {
  eval(«alert(‘Привет)»);   // Отсутствует ‘вызовет ошибку
}
catch(err) {
  document.getElementById(«demo»).innerHTML = err.name;
}

Попробуйте сами »


Ошибка TypeError

Ошибка TypeError генерируется, если вы используете значение, которое находится за пределами диапазона ожидаемых типов:

Пример

var num = 1;
try {
  num.toUpperCase();   // Вы не можете преобразовать число в верхний регистр
}
catch(err) {
  document.getElementById(«demo»).innerHTML = err.name;
}

Попробуйте сами »


Ошибка URIError (Единый идентификатор ресурса)

Ошибка URIError генерируется, если вы используете недопустимые символы в функции URI:

Пример

try {
  decodeURI(«%%%»);   // Вы не можете декодировать знаки процента URI
}
catch(err) {
  document.getElementById(«demo»).innerHTML = err.name;
}

Попробуйте сами »


Нестандартные свойства объекта ошибок

Mozilla и Microsoft определяют некоторые нестандартные свойства объекта ошибки:

fileName (Mozilla)
lineNumber (Mozilla)
columnNumber (Mozilla)
stack (Mozilla)
description (Microsoft)
number (Microsoft)

Не используйте эти свойства на общедоступных веб-сайтах. Они не будут работать во всех браузерах.


Полный справочник ошибок

Чтобы получить полную информацию об объекте Error, перейдите в Полный справочник ошибок JavaScript ..


Обработка ошибок в JS – та еще головная боль. Не ошибусь, если скажу, что ошибки – самое слабое место всего языка. При чем проблема эта составная и состоит она из двух других проблем: сложность отлова ошибки в асинхронном коде и плохо спроектированный объект Error. Вторая, на самом, деле менее очевидная, поэтому с нее и начнем.

Корень зла

Реализация объекта ошибки в JS – одна из самых ужасных, которые я когда либо встречал. Мало того сама реализация отличается в различных движках. Объект спроектирован (и развивается) так будто ни до, ни после возникновения JS с ошибками вообще не работали. Я даже не знаю с чего начать. Этот объект не является программно-интерпретируемым, так как все важные значения являются склеенными строками. Отсутствует механизм захвата стека вызовов и алгоритм расширения ошибки.

Результатом этого является то, что каждый разработчик вынужден самостоятельно принимать решение в каждом отдельном случае, но, как доказали ученые, выбор вызывает у людей дискомфорт, поэтому очень часто ошибки просто-напросто игнорируются и попадают в основной поток. Так же, достаточно часто, вместо ошибки вы можете получить Array или Object, спроектированные «на свое усмотрение». Поэтому вместо единой системы обработки ошибок мы сталкиваемся с набором уникальных правил для каждого отдельного случая.
И это не только мои слова, тот же TJ Holowaychuck написал об этом в своем письме прощаясь с сообществом nodejs.

Как же решить проблему? Создать единую стратегию формирования и обработки сообщения об ошибке! Разработчики Google предлагают пользователям V8 собственный набор инструментов, которые облегчают эту задачу. И так приступим.

MyError

Давайте начнем с создания собственного объекта ошибки. В классической теории, все что вы можете сделать – создать экземпляр 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, name и code

Следующим пунктом в решении проблемы стоит идентификация ошибки. Для того чтобы программно ее обработать и принять решение о дальнейших действиях: выдать пользователю сообщение или завершить работу. Поле message не дает таких возможностей: парсить сообщение регулярным выражением не представляется разумным. Как же тогда отличить ошибку неверного параметра от ошибки соединения? В самом nodejs для этого используется поле code. При этом в стандарте для классификации ошибок предписывается использовать поле name. Но используются они по разному, поэтому рекомендую использовать для этого следующие правила:

  1. Поле name должно содержать значение в «скачущем» регистре: MyError.
  2. Поле code должно содержать значение разделенное подчеркиванием, символы должны быть в верхнем регистре: SOMETHING_WRONG.
  3. Не используйте в поле code слово ERROR.
  4. Значение в name создано для калссификации ошибок, поэтому лучше использовать ConnectionError либо MongoError, вместо MongoConnectionError.
  5. Значение code должно быть уникальным.
  6. Поле message должно формироваться на основе значения code и переданных переменных параметров.
  7. Для успешной обработки ошибки желательно добавить дополнительные сведения в сам объект.
  8. Дополнительные значения должны быть примитивами: не стоит передавать в объект ошибки соединение с базой данных.

Пример:

Чтобы создать отчет об ошибке чтения файла по причине того что файл отсутствует можно указать следующие значения: 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-странице v8 посвященной ErrorTraceAPI.

Заключение

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

Автор: rumkin

Источник

Данная статья является переводом. Ссылка на оригинал.

В статье рассмотрим:

  1. Объект Error
  2. Try…catch
  3. Throw
  4. Call stack
  5. Наименование функций
  6. Парадигму асинхронного программирования Promise

Представьте, как разрабатываете RESTful web API на Node.js.

  • Пользователи отправляют запросы к серверу для получения данных.
  • Вопрос времени, когда в программу придут значения, которые не ожидались.
  • В таком случае, когда программа получит эти значения, пользователи будут рады видеть подробное и конкретное описание ошибки.
  • В случае когда нет соответствующего обработчика ошибки, отобразится стандартное сообщение об ошибке. Таким образом, пользователь увидит сообщение об ошибке «Невозможно выполнить запрос», что не несет полезной информации о том, что произошло.
  • Поэтому стоит уделять внимание обработке ошибок, чтобы наша программа стала безопасной, отказоустойчивой, высокопроизводительной и без ошибок.

Анатомия Объекта Error

Первое, с чего стоит начать изучение – это объект Error.

Разберем на примере:

        throw new Error('database failed to connect');
    

Здесь происходят две вещи: создается объект Error и выбрасывается исключение.

Начнем с рассмотрения объекта Error, и того, как он работает. К ключевому слову throw вернемся чуть позже.

Объект Error представляет из себя реализацию функции конструктора, которая использует набор инструкций (аргументы и само тело конструктора) для создания объекта.

Тем не менее, что же такое объекты ошибок? Почему они должны быть однородными? Это важные вопросы, поэтому давайте перейдем к ним.

Первым аргументом для объекта Error является его описание.

Описание – это понятная человеку строка объекта ошибки. Также эта строка появляется в консоли, когда что-то пошло не так.

Объекты ошибок также имеют свойство name, которое рассказывает о типе ошибки. Когда создается нативный объект ошибки, то свойство name по умолчанию содержит Error. Вы также можете создать собственный тип ошибки, расширив нативный объект ошибки следующим образом:

        class FancyError extends Error {
    constructor(args){
        super(args);
        this.name = "FancyError"
    }
}

console.log(new Error('A standard error'))
// { [Error: A standard error] }

console.log(new FancyError('An augmented error'))
// { [Your fancy error: An augmented error] name: 'FancyError' }

    

Обработка ошибок становится проще, когда у нас есть согласованность в объектах.

Ранее мы упоминали, что хотим, чтобы объекты ошибок были однородными. Это поможет обеспечить согласованность в объекте ошибки.

Теперь давайте поговорим о следующей части головоломки – throw.

Ключевое слово Throw

Создание объектов ошибок – это не конец истории, а только подготовка ошибки к отправке. Отправка ошибки заключается в том, чтобы выбросить исключение. Но что значит выбросить? И что это значит для нашей программы?

Throw делает две вещи: останавливает выполнение программы и находит зацепку, которая мешает выполнению программы.

Давайте рассмотрим эти идеи одну за другой:

  • Когда JavaScript находит ключевое слово throw, первое, что он делает – предотвращает запуск любых других функций. Остановка снижает риск возникновения любых дальнейших ошибок и облегчает отладку программ.
  • Когда программа остановлена, JavaScript начнет отслеживать последовательную цепочку функций, которые были вызваны для достижения оператора catch. Такая цепочка называется стек вызовов (англ. call stack). Ближайший catch, который находит JavaScript, является местом, где возникает выброшенное исключение. Если операторы try/catch не найдены, тогда возникает исключение, и процесс Node.js завершиться, что приведет к перезапуску сервера.

Бросаем исключения на примере

Мы рассмотрели теорию, а теперь давайте изучим пример:

        function doAthing() {
    byDoingSomethingElse();
}

function byDoingSomethingElse() {
    throw new Error('Uh oh!');
}

function init() {
    try {
        doAthing();
    } catch(e) {
        console.log(e);
        // [Error: Uh oh!]
    }
}

init();

    

Здесь в функции инициализации init() предусмотрена обработка ошибок, поскольку она содержит try/catch блок.

init() вызывает функцию doAthing(), которая вызывает функцию byDoingSomethingElse(), где выбрасывается исключение. Именно в этот момент ошибки, программа останавливается и начинает отслеживать функцию, вызвавшую ошибку. Далее в функции init() и выполняет оператор catch. С помощью оператора catch мы решаем что делать: подавить ошибку или даже выдать другую ошибку (для распространения вверх).

Стек вызовов

То, что показано в приведенном выше примере – это проработанный пример стека вызовов. Как и большинство языков, JavaScript использует концепцию, известную как стек вызовов.

Но как работает стек вызовов?

Всякий раз, когда вызывается функция, она помещается в стек, а при завершении удаляется из стека. Именно от этого стека мы получили название «трассировки стека».

Трассировка стека – это список функций, которые были вызваны до момента, когда в программе произошло исключение.

Она часто выглядит так:

        Error: Uh oh!
at byDoingSomethingElse (/filesystem/aProgram.js:7:11)
at doAthing (/filesystem/aProgram.js:3:5)
at init (/filesystem/aProgram.js:12:9)
at Object.<anonymous> (/filesystem/aProgram.js:19:1)
at Module._compile (internal/modules/cjs/loader.js:689:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
at Module.load (internal/modules/cjs/loader.js:599:32)
at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
at Function.Module._load (internal/modules/cjs/loader.js:530:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)

    

На этом этапе вам может быть интересно, как стек вызовов помогает нам с обработкой ошибок Node.js. Давайте поговорим о важности стеков вызовов.

Стек вызовов предоставляет «хлебные крошки», помогая проследить путь, который привел к исключению(ошибке).

Почему у нас должны быть функции без имен? Иногда в наших программах мы хотим определить маленькие одноразовые функции, которые выполняют небольшую задачу. Мы не хотим утруждать себя задачей давать им имена, но именно эти анонимные функции могут вызвать у нас всевозможные головные боли. Анонимная функция удаляет имя функции из нашего стека вызовов, что делает наш стек вызовов значительно более сложным в использовании.

Обратите внимание, что присвоить имена функциям в JavaScript не так просто. Итак, давайте кратко рассмотрим различные способы определения функций, и рассмотрим некоторые ловушки в именовании функций.

Как называть функции

Чтобы понять, как называть функции, давайте рассмотрим несколько примеров:

        // анонимная функция
const one = () => {};

// анонимная функция
const two = function () {};

// функция с явным названием
const three = function explicitFunction() {};

    

Вот три примера функций.

Первая – это лямбда (или стрелочная функция). Лямбда функции по своей природе анонимны. Не запутайтесь. Имя переменной one не является именем функции. Имя функции следующее за ключевым словом function необязательно. Но в этом примере мы вообще ничего не передаем, поэтому наша функция анонимна.

Примечание

Не помогает и то, что некоторые среды выполнения JavaScript, такие как V8, могут иногда угадывать имя вашей функции. Это происходит, даже если вы его не даете.

Во втором примере мы получили функциональное выражение. Это очень похоже на первый пример. Это анонимная функция, но просто объявленная с помощью ключевого слова function вместо синтаксиса жирной стрелки.

В последнем примере объявление переменной с подходящим именем explicitFunction. Это показывает, что это единственная функция, у которой соответствующее имя.

Как правило, рекомендуется указывать это имя везде, где это возможно, чтобы иметь более удобочитаемую трассировку стека.

Обработка асинхронных исключений

Мы познакомились с объектом ошибок, ключевым словом throw, стеком вызовов и наименованием функций. Итак, давайте обратим наше внимание на любопытный случай обработки асинхронных ошибок. Почему? Потому что асинхронный код ведет себя не так, как ожидаем. Асинхронное программирование необходимо каждому программисту на Node.js.

Javascript – это однопоточный язык программирования, а это значит, что Javascript запускается с использованием одного процессора. Из этого следует, что у нас есть блокирующий и неблокирующий код. Блокирующий код относится к тому, будет ли ваша программа ожидать завершения асинхронной задачи, прежде чем делать что-либо еще. В то время как неблокирующий код относится к тому, где вы регистрируете обратный вызов (callback) для выполнения после завершения задачи.

Стоит упомянуть, что есть два основных способа обработки асинхронности в JavaScript: promises (обещания или промисы) и callback (функция обратного вызова). Мы намеренно игнорируем async/wait, чтобы избежать путаницы, потому что это просто сахар поверх промисов.

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

Примечание

Существует множество способов конвертировать код на основе callback-ов в promises. Например, вы можете использовать такую утилиту, как promisify, или обернуть свои обратные вызовы в промисы, например, так:

        var request = require('request'); //http wrapped module
function requestWrapper(url, callback) {
    request.get(url, function (err, response) {
        if (err) {
            callback(err);
        } else {
            callback(null, response);
        }
    })
}

    

Мы разберемся с этой ошибкой, обещаю!

Давайте взглянем на анатомию обещаний.

Промисы в JavaScript – это объект, представляющий будущее значение. Promise API позволяют нам моделировать асинхронный код так же, как и синхронный. Также стоит отметить, что обещание обычно идет в цепочке, где выполняется одно действие, затем другое и так далее.

Но что все это значит для обработки ошибок Node.js?

Промисы элегантно обрабатывают ошибки и перехватывают любые ошибки, которые им предшествовали в цепочке. С помощью одного обработчика обрабатывается множество ошибок во многих функциях.

Изучим код ниже:

        function getData() {
    return Promise.resolve('Do some stuff');
}

function changeDataFormat() {
    // ...
}

function storeData(){
    // ...
}

getData()
    .then(changeDataFormat)
    .then(storeData)
    .catch((e) => {
        // Handle the error!
    })


    

Здесь видно, как объединить обработку ошибок для трех различных функций в один обработчик, т. е. код ведет себя так же, как если бы три функции заключались в синхронный блок try/catch.

Отлавливать или не отлавливать?

На данном этапе стоит спросить себя, повсеместно ли добавляется .catch к промисам, поскольку это опционально. Из-за проблем с сетью, аппаратного сбоя или истекшего времени ожидания в асинхронных вызовах возникает исключение. По этим причинам указывайте программе, что делать в случаях невыполнения промиса.

Запомните «Золотое правило» – каждый раз обрабатывать исключения в обещаниях.

Риски асинхронного try/catch

Мы приближаемся к концу в нашем путешествии по обработке ошибок в Node.js. Пришло время поговорить о ловушках асинхронного кода и оператора try/catch.

Вам может быть интересно, почему промис предоставляет метод catch, и почему мы не можем просто обернуть нашу реализацию промиса в try/catch. Если бы вы сделали это, то результаты были бы не такими, как вы ожидаете.

Рассмотрим на примере:

        try {
    throw new Error();
} catch(e) {
    console.log(e); // [Error]
}

try {
    setTimeout(() => {
        throw new Error();
    }, 0);
} catch(e) {
    console.log(e); // Nothing, nada, zero, zilch, not even a sound
}
    

try/catch по умолчанию синхронны, что означает, что если асинхронная функция выдает ошибку в синхронном блоке try/catch, ошибка не будет брошена.

Однозначно это не то, что ожидаем.

***

Подведем итог! Необходимо использовать обработчик промисов, когда мы имеем дело с асинхронным кодом, а в случае с синхронным кодом подойдет try/catch.

Заключение

Из этой статьи мы узнали:

  • как устроен объект Error;
  • научились создавать свои собственные ошибки;
  • как работает стек вызовов;
  • практики наименования функций, для удобочитаемой трассировки стека;
  • как обрабатывать асинхронные исключения.

***

Материалы по теме

  • 🗄️ 4 базовых функции для работы с файлами в Node.js
  • Цикл событий: как выполняется асинхронный JavaScript-код в Node.js
  • Обработка миллионов строк данных потоками на Node.js

Понравилась статья? Поделить с друзьями:
  • Solution provider failed to load with error 2146762486
  • Solution model setup check error summary
  • Solta whatsapp ошибка 122
  • Solr error opening new searcher
  • Solpi m tsd 500ba ошибка l