12Sep

node_js_by_myvushka-d8yrtqh
Несколько особняком в 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 %s\n message: %s\n 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("Ошибка %s\n сообщение: %s\n стек: %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.

Код урока вы можете скачать здесь

ogimage

Материалы для урока взяты из следующего скринкаста.

We are looking forward to meeting you on our website soshace.com

23. Уроки Node.js. Домены, “асинхронный try..catch”. Часть 1.

Всем привет! Тема этого выпуска: Домены.

Домены – это возможность Node.js, который отсутствует как в обычном JavaScript, так и в JavaScript в браузерных реализациях. Домены предназначены для того, чтобы перехватывать любые асинхронные ошибки, например, если мы взглянем на сервер ниже, который мы разбирали в одном из предыдущих статей (загрузите себе код урока по ссылке для удобства), то увидим, что пока он работает – все нормально