Несколько особняком в 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.
Код урока вы можете скачать здесь
Материалы для урока взяты из следующего скринкаста.
We are looking forward to meeting you on our website soshace.com