
Несколько особняком в Node.js стоит наследование отстроенного объекта ошибки Error. На этом примере я объясню сначала, зачем оно может понадобиться, а потом – как его делать правильно.
// Inheritance from Error  
  
var util = require('util');  
  
var phrases = {  
  "Hello": "Hello",  
  "world": "World"  
};  
  
  
function getPhrase(name) {  
  if (!phrases[name]) {  
    throw new Error("There is no such a pharase: " + name);  
  }  
  return phrases[name];  
}  
  
  
function makePage(url) {  
  
  if (url != 'index.html') {  
    throw new Error("There is no such a pharase");  
  }  
  
  return util.format("%s, %s!", getPhrase("Hello"), getPhrase("world"));  
}  
  
  
var page = makePage('index.html');  
console.log(page);  
Здесь код состоит из двух функций. Первая – getPhrase.
function getPhrase(name) {
    if (!phrases[name]) {
        throw new Error("There is no such a phrase: " + name);
    }
    return phrases[name];
}Это самая простейшая функция по интернационализации, которая только может быть. Она берет название фразы (name) и возвращает соответствующие данные, если они есть, а если нет – то исключение (“There is no such a phrase”).
Вторая функция – makePage:
function makePage(url) {  
  
  if (url != 'index.html') {  
    throw new Error("There is no such a page");  
  }  
  
  return util.format("%s, %s!", getPhrase("Hello"), getPhrase("world"));  
}  
Эта функция получает url. Сейчас она умеет работать только с index.html. Поэтому, для остальных она выдает ошибку, а если это index.html, то возвращает его отформатированную строку. Давайте посмотрим, как работает вот такой код:
var page = makePage('index.html');  
console.log(page); 
Запускаем. Работает. «Hello World» вывел отформатированную строку. Все верно. А теперь давайте добавим к этому коду правильную обработку ошибок. Например, если мы получили некорректный url, несуществующий, тогда makePage бросит ошибку «Нет такой страницы», и если представить себе, что это веб сервер, то это означает, что мы должны вывести пользователю сообщение: 404 – страница не найдена. Это один вариант ошибки, одна обработка. А посмотрим, что будет, если неизвестна фраза. Если в каком-то месте кода мы вызвали получение фразы, например, вот так:
return util.format("%s, %s!", getPhrase("Hell"), getPhrase("world"));То «Нет такой фразы» – это уже другая ошибка. И тогда нужно уже не 404 сделать, а статус 500 и обязательно написать уведомление системному администратору, что что-то тут не так, словарь не полон, его срочно нужно поправить. Это программная ошибка, поэтому обрабатывать ее нужно по-другому.
К сожалению, в текущем коде
var page = makePage('index.html');  
console.log(page); 
даже если я добавлю try catch вокруг функции makePage, все равно понять где какая ошибка нельзя. И то, и другое это просто ошибки класса error. С точки зрения объектно ориентированного программирования разумным способом решения этой проблемы будет сделать свои объекты ошибки для разных случаев. Здесь это будет
function getPhrase(name) {  
  if (!phrases[name]) {  
    throw new PhraseError("There is no such a phrase: " + name);  
  }  
  return phrases[name];  
} 
А здесь
function makePage(url) {  
  
  if (url != 'index.html') {  
    throw new HttpError(404, "There is no such a page");  
  } 
И в его конструкторе мы заодно указали статус – 404. Соответственно, для тех ошибок, которые мы должны показать пользователю вида Http мы будем использовать вот эти ошибки: HttpError, а для ошибок, связанных с переводом – PhraseError(“Нет такой фразы: ” + name).
Объявим соответствующие классы при помощи util inherits. Например, вот так:
function PhraseError(message) {  
  this.message = message;  
}  
util.inherits(PhraseError, Error);  
PhraseError.prototype.name = 'PhraseError';  
И вот так:
function HttpError(status, message) {  
  this.status = status;  
  this.message = message;  
}  
util.inherits(HttpError, Error);  
HttpError.prototype.name = 'HttpError';  
Сразу же посмотрим на особенности работы с объектом ошибок. Какие свойства нам здесь нужны? Первое, конечно же, message. Для того, чтобы поставить message, мне нужно сделать это вручную. Это есть такая особенность работы с ошибками. То есть, не вызов стандартного родителя суперкласса, то есть, Error.apply(this, arguments): – вот так мы обычно вызываем конструктор суперкласса. В данном случае, ничего он полезного нам не сделает. Необходимо поставить вручную.
Следующее свойство, которое есть у всех встроенных ошибок, это name. По этому свойству мы можем абсолютно точно понять, что за ошибка у нас есть. Оно у всех встроенных ошибках в прототипах есть, поэтому и сюда тоже мы запишем.
Наконец, последнее свойство, которое нам будет важно, это stack. О нем чуть позже.
Итак, давайте сейчас запущу код
var util = require('util');
var phrases = {
    "Hello": "Hello",
    "world": "World"
};
// message name stack
function PhraseError(message) {
    this.message = message;
}
util.inherits(PhraseError, Error);
PhraseError.prototype.name = 'PhraseError';
function HttpError(status, message) {
    this.status = status;
    this.message = message;
}
util.inherits(HttpError, Error);
HttpError.prototype.name = 'HttpError';
function getPhrase(name) {
    if (!phrases[name]) {
        throw new PhraseError("There is no such a phrase: " + name);
    }
    return phrases[name];
}
function makePage(url) {
    if (url != 'index.html') {
        throw new HttpError(404, "There is no such a page");
    }
    return util.format("%s, %s!", getPhrase("*****"), getPhrase("world"));
}
try {
    var page = makePage('index');
    console.log(page);
} catch (e) {
    if (e instanceof HttpError) {
        console.log(e.status, e.message);
    } else {
        console.error("Error %sn message: %sn stack: %s", e.name, e.message, e.stack);
    }
}
Как видим, тут makePage(“index”); – это неизвестный url, поэтому мы должны получить ошибку HttpError. Такую ошибку мы обрабатываем вот так:
console.log(e.status, e.message);
Просто выводим без всякой паники. Запускаем:
node errors
Все работает.
Теперь посмотрим на другой вариант, а именно, если url правильный (обратите внимание что жирным в коде ниже выделены измененные строчки), но ошибка произошла в программе, то есть, высветилась какая-то непонятная фраза, которой точно нету. В данном случае, если ошибка какая-то другая, мы должны на нее отреагировать совсем иначе. Паника, кошмар, программная ошибка! Срочно всех поднимаем и исправляем! Эта ветка кода будет действовать для все программных ошибок, в том числе и для встроенных, а не только для PhraseError
function makePage(url) {  
  if (url != 'index.html') {  
    throw new HttpError(404, "Нет такой страницы");  
  }  
  return util.format("%s, %s!", getPhrase("*****
"), getPhrase("world"));  
}  
  
try {  
  var page = makePage('index.html
');  
  console.log(page);  
} catch (e) {  
  if (e instanceof HttpError) {  
    console.log(e.status, e.message);  
  } else {  
    console.error("Ошибка %sn сообщение: %sn стек: %s", e.name, e.message, e.stack);  
  }  
}  
Запускаем. Вывел, действительно, console.error. Посмотрим на свойства. Все правильно. А вот свойство stack должно хранить информацию о том, где, в каком файле произошла эта ошибка, что ей предшествовало. В конструкторах я не произвел никаких специальных действий, которые правильно поставят stack. И в стандартных java script их вообще не предусмотрено. Я просто создаю новый объект. На самом деле, есть разные круги, которые позволяют получить stack, но здесь они нам не понадобятся, поскольку в V8 есть специальная JS команда, которая не входит в стандарт, но позволяет получить stack. Выглядит она так:
function PhraseError(message) {
    this.message = message;
    Error.captureStackTrace(this);
}Эта команда получает текущий stack, то есть, последовательность сложенных вызовов, которые при текущем месте кода, и сохраняют его в this, то есть, в объекте ошибки. Давайте посмотри, что у нас получится сейчас. Вот теперь оно вывело stack, то есть, то место, где это все и произошло. Но если посмотреть внимательно ошибка произошла, на самом деле, вот здесь:
throw new PhraseError("There is no such a pharase: " + name);Нас интересует, то, что произошло тут, и как к этому мы дошли. То, что происходило внутри PhraseError, нас, в принципе, не интересует. Эта строчка stack в данному случае лишняя. Чтобы ее убрать, в captureStackTrace предусмотрен второй необязательный специальный параметр. Это функция, до которой будет собираться StackTrace. То есть, если здесь я укажу текущий конструктор
Т.е. в функции HttpError
Error.captureStackTrace(this, HttpError);
и в функции PhraseError
Error.captureStackTrace(this, PhraseError);
сделаю следующие изменения.
То, при этом, тот stack, который внутри этой функции, не будет здесь показан, не будет собран. Давайте проверим. Я перезапустил, и лишняя строка теперь вырезана.
Итак, мы получили унаследованные объекты ошибки с правильными свойствами message, name и с возможностью вывести stack.
Код урока вы можете скачать здесь

Материалы для урока взяты из следующего скринкаста.
We are looking forward to meeting you on our website blog.soshace.com


