8. Уроки Node.js. Наследование от ошибок Error
Несколько особняком в Node.js стоит наследование отстроенного объекта ошибки Error. На этом примере я объясню сначала, зачем оно может понадобиться, а потом – как его делать правильно.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | // 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.
1 2 3 4 5 6 | 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:
1 2 3 4 5 6 7 8 | 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, то возвращает его отформатированную строку. Давайте посмотрим, как работает вот такой код:
1 2 | var page = makePage('index.html'); console.log(page); |
Запускаем. Работает. «Hello World» вывел отформатированную строку. Все верно. А теперь давайте добавим к этому коду правильную обработку ошибок. Например, если мы получили некорректный url, несуществующий, тогда makePage бросит ошибку «Нет такой страницы», и если представить себе, что это веб сервер, то это означает, что мы должны вывести пользователю сообщение: 404 – страница не найдена. Это один вариант ошибки, одна обработка. А посмотрим, что будет, если неизвестна фраза. Если в каком-то месте кода мы вызвали получение фразы, например, вот так:
1 | return util.format("%s, %s!", getPhrase("Hell"), getPhrase("world")); |
То «Нет такой фразы» – это уже другая ошибка. И тогда нужно уже не 404 сделать, а статус 500 и обязательно написать уведомление системному администратору, что что-то тут не так, словарь не полон, его срочно нужно поправить. Это программная ошибка, поэтому обрабатывать ее нужно по-другому.
К сожалению, в текущем коде
1 2 | var page = makePage('index.html'); console.log(page); |
даже если я добавлю try catch вокруг функции makePage, все равно понять где какая ошибка нельзя. И то, и другое это просто ошибки класса error. С точки зрения объектно ориентированного программирования разумным способом решения этой проблемы будет сделать свои объекты ошибки для разных случаев. Здесь это будет
1 2 3 4 5 6 | function getPhrase(name) { if (!phrases[name]) { throw new PhraseError("There is no such a phrase: " + name); } return phrases[name]; } |
А здесь
1 2 3 4 5 | function makePage(url) { if (url != 'index.html') { throw new HttpError(404, "There is no such a page"); } |
И в его конструкторе мы заодно указали статус – 404. Соответственно, для тех ошибок, которые мы должны показать пользователю вида Http мы будем использовать вот эти ошибки: HttpError, а для ошибок, связанных с переводом – PhraseError(“Нет такой фразы: ” + name).
Объявим соответствующие классы при помощи util inherits. Например, вот так:
1 2 3 4 5 | function PhraseError(message) { this.message = message; } util.inherits(PhraseError, Error); PhraseError.prototype.name = 'PhraseError'; |
И вот так:
1 2 3 4 5 6 | 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. О нем чуть позже.
Итак, давайте сейчас запущу код
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | 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. Такую ошибку мы обрабатываем вот так:
1 | console.log(e.status, e.message); |
Просто выводим без всякой паники. Запускаем:
node errors
Все работает.
Теперь посмотрим на другой вариант, а именно, если url правильный (обратите внимание что жирным в коде ниже выделены измененные строчки), но ошибка произошла в программе, то есть, высветилась какая-то непонятная фраза, которой точно нету. В данном случае, если ошибка какая-то другая, мы должны на нее отреагировать совсем иначе. Паника, кошмар, программная ошибка! Срочно всех поднимаем и исправляем! Эта ветка кода будет действовать для все программных ошибок, в том числе и для встроенных, а не только для PhraseError
1 2 3 4 5 | function makePage(url) { if (url != 'index.html') { throw new HttpError(404, "Нет такой страницы"); } return util.format("%s, %s!", getPhrase(" |
*****
1 2 3 4 5 | "), getPhrase("world")); } try { var page = makePage(' |
index.html
1 2 3 4 5 6 7 8 9 | '); 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. Выглядит она так:
1 2 3 4 | function PhraseError(message) { this.message = message; Error.captureStackTrace(this); } |
Эта команда получает текущий stack, то есть, последовательность сложенных вызовов, которые при текущем месте кода, и сохраняют его в this, то есть, в объекте ошибки. Давайте посмотри, что у нас получится сейчас. Вот теперь оно вывело stack, то есть, то место, где это все и произошло. Но если посмотреть внимательно ошибка произошла, на самом деле, вот здесь:
1 | throw new PhraseError("There is no such a pharase: " + name); |
Нас интересует, то, что произошло тут, и как к этому мы дошли. То, что происходило внутри PhraseError, нас, в принципе, не интересует. Эта строчка stack в данному случае лишняя. Чтобы ее убрать, в captureStackTrace предусмотрен второй необязательный специальный параметр. Это функция, до которой будет собираться StackTrace. То есть, если здесь я укажу текущий конструктор
Т.е. в функции HttpError
1 | Error.captureStackTrace(this, HttpError); |
и в функции PhraseError
1 | Error.captureStackTrace(this, PhraseError); |
сделаю следующие изменения.
То, при этом, тот stack, который внутри этой функции, не будет здесь показан, не будет собран. Давайте проверим. Я перезапустил, и лишняя строка теперь вырезана.
Итак, мы получили унаследованные объекты ошибки с правильными свойствами message, name и с возможностью вывести stack.
Код урока вы можете скачать здесь
Материалы для урока взяты из следующего скринкаста.
We are looking forward to meeting you on our website soshace.com
Sign in \ Sign Up
Or use email\username to sign in
By Signing In \ Signing Up, you agree to our privacy policy