Несколько особняком, в 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 |
var util = require('util'); var phrases = { "Hello": "Привет", "world": "мир" }; function getPhrase(name){ if(!phrases[name]){ throw new Error("Нет такой фразы: " + name); } return phrases[name]; } function makePage(url){ if(url != 'index.html'){ throw new Error("Нет такой страницы"); } return util.format("%s, %s!", getPhrase("Hello"), getPhrase("world")); } var page = makePage('index.html'); console.log(page); |
я сначала объясню, зачем оно может понадобиться, а потом как его делать правильно.
Здесь код состоит, по сути, из двух функций. Первая «function getPhrase(name)» это самая простейшая функция по интернационализации, которая только может быть. Она берет название фразы «name» и возвращает соответствующий перевод, если он есть. А если нет, то исключение. Вторая «function makePage(url)» получает «url», сейчас она умеет работать только с «index.html», поэтому, для остальных, она кидает ошибку, а если это «index.html» то возвращает такую вот форматированную строку — «return util.format(«%s, %s!», getPhrase(«Hello»), getPrase(«world»));». Давайте посмотрим, как работает такой код, запускаем
пока работает.
А теперь давайте добавим к этому коду правильную обработку ошибок. Например, если мы получили не корректный URL, несуществующий,
21 22 23 |
var page = makePage('index'); console.log(page); |
тогда «function makePage(url)» бросит ошибку, «Нет такой страницы». Если представить себе, что это веб сервер, то означает, что мы должны вывести юзеру сообщение «HTTP 404» Страница не найдена. Это один вариант ошибки, одна обработка. А посмотрим, что если фраза неизвестна. Если в каком то месте кода, мы вызвали получение фразы например вот так
15 16 17 18 19 20 21 |
function makePage(url){ if(url != 'index.html'){ throw new Error("Нет такой страницы"); } return util.format("%s, %s!", getPhrase("Hell"), getPhrase("world")); } |
Нет такой фразы, это уже совсем другая ошибка. И тогда нужно уже не 404 сделать, а статус 500 и написать уведомление системному администратору, что что-то тут не так, что словарь не полон и его срочно нужно поправить. Это программная ошибка, по этому обрабатывать ее нужно по другому.
К сожалению в текущем коде, даже если я добавлю «try/catch» к функции «makePage», все равно понять где какая ошибка, нельзя. И то и другое, это просто ошибки — класс «Error». С точки зрения объектно ориентированного программирования разумный способ решить эту проблему, это сделать свои объекты ошибки для разных случаев.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
var util = require('util'); var phrases = { "Hello": "Привет", "world": "мир" }; function getPhrase(name){ if(!phrases[name]){ throw new PhraseError("Нет такой фразы: " + name); // HTTP 500, Уведомление! } return phrases[name]; } function makePage(url){ if(url != 'index.html'){ throw new HttpError(404, "Нет такой страницы"); // HTTP 404 } return util.format("%s, %s!", getPhrase("Hello"), getPhrase("world")); } var page = makePage('index'); console.log(page); |
Это будет «PhraseError» и «HttpError», в ее конструкторе мы заодно укажем статус.
Что ж, объявим соответствующие классы при помощи «util.inherits»
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 |
var util = require('util'); var phrases = { "Hello": "Привет", "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("Нет такой фразы: " + name); // HTTP 500, Уведомление! } return phrases[name]; } function makePage(url){ if(url != 'index.html'){ throw new HttpError(404, "Нет такой страницы"); // HTTP 404 } return util.format("%s, %s!", getPhrase("Hello"), 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("Ошибка %s\n сообщение: %s\n стек: %s", e.name, e.message, e.stack); } } |
Сразу же посмотрим на особенности работы над объектом ошибок. Какие нам свойства здесь важны. Первое, конечно же, «message». Для того, чтобы поставить «message» мне нужно сделать это вручную. Это есть особенность такая, работы с ошибками. То есть не вызов стандартного родителя суперкласса — вот так мы обычно конструктор супер класса вызываем
1 |
Error.apply(this, arguments); |
В данном случае ничего полезного он нам не сделает, необходимо поставить вручную
1 |
this.message = message; |
Следующие свойство, которое есть у всех встроенных ошибок, это «name». По свойству «name» мы можем точно понять, что за ошибка у нас есть. Оно у всех встроенных ошибках в прототипе у нас есть, по этому и сюда тоже мы запишем.
1 |
PhaseError.prototype.name = 'PhraseError'; |
и
1 |
HttpError.prototype.name = 'HttpError'; |
Наконец, последнее свойство, которое нам будет важно, это «stack». О нем мы поговорим чуть позже.
И так, давайте я сейчас запущу этот код. Как видим тут
36 37 38 39 |
try{ var page = makePage('index'); console.log(page); } catch (e){ |
«makePage(‘index’)» это неизвестный URL, по этому мы должны получить ошибку ‘HttpError’. Такую ошибку мы обрабатываем вот так
39 40 41 42 |
} catch (e){ if (e instanceof HttpError){ console.log(e.status, e.message); } else { |
То есть просто выводим, без всякой паники. Запускаю
Увидели то, что должны были увидеть, все работает.
Теперь посмотрим другой вариант, а именно, если URL правильный, но ошибка произошла в программе, то есть видите, тут какая то не понятная фраза «Hell» вместо «Hello»
29 30 31 32 33 34 35 36 37 38 39 |
function makePage(url){ if(url != 'index.html'){ throw new HttpError(404, "Нет такой страницы"); // HTTP 404 } return util.format("%s, %s!", getPhrase("Hell"), getPhrase("world")); } try{ var page = makePage('index.html'); console.log(page); } catch (e){ |
В данном случае, то есть если ошибка какая то другая, мы должны на нее отреагировать тоже иначе. Паника, кошмар, программная ошибка, срочно всех поднимаем, исправляем. Эта ветка кода будет действовать для всех программных ошибок, в том числе и для встроенных, а не только для «PhraseError». Запускаем
Вывело действительно «console.error», посмотрим на свойства —
- Ошибка — «PhraseError», первое правильно
- сообщение — «Нет такой фразы: Hell», тоже правильно
- стек — «undefined»
Стек, это можно сказать самое важное. Свойство «stack» должно хранить информацию о том где, в каком файле произошла эта ошибка, что ей предшествовало.
Здесь, в конструкторах
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// 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'; |
я не произвел никаких действий, специальных которые правильно поставят стек. И в стандартах JavaScript их вообще не предусмотрено. Я просто создаю новый объект.
На самом деле есть разные способы которые все таки позволяют получить стек, но здесь они нам не понадобятся, поскольку в «V8» есть специальная JavaScript команда, которая не входит в стандарт, но позволяет получит стек. Выглядит она так
1 |
Error.captureStackTrace(this); |
Эта команда получает текущий стек, то есть последовательность сложенных вызовов, которые привели к текущему месту кода и сохраняет его в «this», то есть в объекте ошибки. Давайте посмотрим на наш код
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": "Привет", "world": "мир" }; // message name stack function PhraseError(message){ this.message = message; Error.captureStackTrace(this); } 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("Нет такой фразы: " + name); // HTTP 500, Уведомление! } return phrases[name]; } function makePage(url){ if(url != 'index.html'){ throw new HttpError(404, "Нет такой страницы"); // HTTP 404 } return util.format("%s, %s!", getPhrase("Hell"), 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); } } |
Запустим
Вот теперь, оно вывело стек, то есть где это все произошло. Но если посмотреть внимательно, здесь есть небольшой грешок. А именно, ошибка произошла, на самом деле, вот здесь
22 23 24 25 26 27 28 29 |
function getPhrase(name){ if(!phrases[name]){ throw new PhraseError("Нет такой фразы: " + name); // HTTP 500, Уведомление! } return phrases[name]; } |
Нас интересует, то что произошло тут и как мы к этому дошли. То есть, нас интересует вот эта вот часть
до «getPhrase», а то что происходило внутри «new PhraseError» нас в принципе не интересует. То есть вот эта строчка стека
в данном случае лишняя. Чтобы убрать ее, в «captureStackTrace(this)» предусмотрен специальный параметр, второй, необязательный, это функция, до которой будет собираться «stackTrace». То есть если здесь я укажу текущий конструктор
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 47 |
var util = require('util'); var phrases = { "Hello": "Привет", "world": "мир" }; // message name stack function PhraseError(message){ this.message = message; Error.captureStackTrace(this, PhraseError); } util.inherits(PhraseError, Error); PhraseError.prototype.name = 'PhraseError'; function HttpError(status, message){ this.status = status; this.message = message; Error.captureStackTrace(this, HttpError); } util.inherits(HttpError, Error); HttpError.prototype.name = 'HttpError'; function getPhrase(name){ if(!phrases[name]){ throw new PhraseError("Нет такой фразы: " + name); // HTTP 500, Уведомление! } return phrases[name]; } function makePage(url){ if(url != 'index.html'){ throw new HttpError(404, "Нет такой страницы"); // HTTP 404 } return util.format("%s, %s!", getPhrase("Hell"), 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); } } |
то при этом, тот стек который внутри указанной функции, не будет показан в выводе, не будет собран
Я перезапустил и лишняя строка теперь вырезана.
И так мы получили унаследованные объекты ошибки с правильными свойствами «message», «name» и с возможностью вывести «stack».