Биз бир нерсени иштеп чыкканда, биздин тапшырмаларыбызда ката кетириши мүмкүн болгон ар кандай нерселер үчүн өзүбүздүн ката класстарыбыз керек болот. Тармак менен иштөөдө каталар үчүн сизге керек болушу мүмкүн HttpError
, маалымат базасы операциялары үчүн DbError
, издөө үчүн - NotFoundError
ж.б.
Биздин мүчүлүштүктөр message
, name
жана жакшыраак сыяктуу негизги касиеттерди колдоого алышы керек stack
. Бирок алардын да өз касиеттери болушу мүмкүн. Мисалы, объекттер баалуулуктары бар HttpError
касиетке ээ болушу мүмкүн , же .statusCode
404
403
500
JavaScript ар кандай аргументтер менен чалууга мүмкүндүк берет throw
, андыктан техникалык жактан биздин ката класстарыбыздан мураска алуунун кереги жок Error
. Бирок, эгерде сиз мурасты колдонсоңуз, анда ката объекттерин колдонуу менен аныктоо мүмкүн болот obj instanceof Error
. Андыктан мурасты колдонгон жакшы.
Тиркеме өскөн сайын, өзүбүздүн мүчүлүштүктөрүбүз иерархияны түзөт, мисалы, HttpTimeoutError
дан мураска алат HttpError
ж.б.у.с.
error кеңейтүү
Мисал катары, readUser(json)
колдонуучунун маалыматтарын JSON форматында окуй турган функцияны карап көрөлү.
Туура болгонунун мисалы json
:
@A@let json = `{ "name": "John", "age": 30 }`;@A@
Ичинде биз колдонобуз JSON.parse
. Эгерде ал жараксыз болсо, json
анда ката пайда болот SyntaxError
. Бирок ал json
синтаксистик жактан туура болсо да, бул анын жарактуу колдонуучу экенин билдирбейт, туурабы? Керектүү маалыматтар жок болушу мүмкүн. Мисалы, name
жана касиеттери жок болушу мүмкүн age
, бул биздин колдонуучулар үчүн абдан маанилүү.
Биздин функция readUser(json)
JSON маалыматтарын окуп гана койбостон, аны ырастайт ("текшерүү"). Эгерде талап кылынган талаалар жок болсо же маалыматтар туура эмес форматта болсо, анда бул ката болот. Бирок синтаксистик ката эмес SyntaxError
, анткени маалыматтар синтаксистик жактан туура. Бул дагы ката болот.
Келгиле, аны текшерүү катасы деп атайлы ValidationError
жана ал үчүн класс түзөлү. Мындай ката катанын булагы болгон талаа жөнүндө маалыматты камтышы керек.
Биздин класс ValidationError
орнотулган класстан мурас алышы керек Error
.
Класс Error
орнотулган, бул жерде анын үлгү коду, биз эмнени кеңейтип жатканыбызды түшүнүү үчүн:
@A@// "Псевдокод" встроенного класса Error, определённого самим JavaScript
class Error {
constructor(message) {
this.message = message;
this.name = "Error"; // (разные имена для разных встроенных классов ошибок)
this.stack = <стек вызовов>; // нестандартное свойство, но обычно поддерживается
}
}@A@
Эми андан мурас алып ValidationError
, жаңы классты иш жүзүндө сынап көрөлү:
@A@class ValidationError extends Error {
constructor(message) {
super(message); // (1)
this.name = "ValidationError"; // (2)
}
}
function test() {
throw new ValidationError("Упс!");
}
try {
test();
} catch(err) {
alert(err.message); // Упс!
alert(err.name); // ValidationError
alert(err.stack); // список вложенных вызовов с номерами строк для каждого
}@A@
Көңүл буруңуз: сапта (1)
биз ата-эне конструкторду чакырабыз. super
JavaScript бизден бала конструкторду чакырууну талап кылат , андыктан бул милдеттүү. Ата-энелик конструктор орнотот message
.
name
Ата-энелик конструктор ошондой эле үчүн касиетти орнотот "Error"
, ошондуктан сапта (2)
биз аны туура мааниге кайтарабыз.
Келгиле, аны колдонууга аракет кылалы readUser(json)
:
@A@class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
// Использование
function readUser(json) {
let user = JSON.parse(json);
if (!user.age) {
throw new ValidationError("Нет поля: age");
}
if (!user.name) {
throw new ValidationError("Нет поля: name");
}
return user;
}
// Рабочий пример с try..catch
try {
let user = readUser('{ "age": 25 }');
} catch (err) {
if (err instanceof ValidationError) {
alert("Некорректные данные: " + err.message); // Некорректные данные: Нет поля: name
} else if (err instanceof SyntaxError) { // (*)
alert("JSON Ошибка Синтаксиса: " + err.message);
} else {
throw err; // неизвестная ошибка, пробросить исключение (**)
}
}
try..catch
Жогорудагы коддогу блок биздин , ошондой эле ValidationError
орнотулган .SyntaxError
JSON.parse
instanceof
Катанын белгилүү бир түрүн текшерүү үчүн (*)
кантип колдонгонубузга көңүл буруңуз
Биз ошондой эле түрүн колдонуп текшере алабыз err.name
:
// ...
// вместо (err instanceof SyntaxError)
} else if (err.name == "SyntaxError") { // (*)
// ...@A@
c версиясы instanceof
алда канча жакшыраак, анткени келечекте биз аны кеңейтип ValidationError
, аны PropertyRequiredError
. Ал эми текшерүү instanceof
жаңы тукум кууп өткөн класстар үчүн иштей берет. Демек, бул келечек үчүн.
Эгер ал catch
белгисиз катага туш болсо, анда аны (**)
. Блок catch
валидация каталарын жана синтаксистик каталарды кантип иштетүүнү гана билет жана каталардын башка түрлөрүн (коддогу жана башка бүдөмүк каталардан улам) сыртка чыгарышы керек.
Андан ары мурас
Класс ValidationError
өтө жалпы. Көп нерселер туура эмес болуп кетиши мүмкүн. Мүлк жок же туура эмес түзүлүшү мүмкүн (мисалы, жаштын мааниси катары сап age
). Ошондуктан, жетишпеген касиеттер үчүн биз конкреттүү класс жасайбыз PropertyRequiredError
. Ал жок болгон мүлк жөнүндө кошумча маалымат алып келет.
@A@class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
class PropertyRequiredError extends ValidationError {
constructor(property) {
super("Нет свойства: " + property);
this.name = "PropertyRequiredError";
this.property = property;
}
}
// Применение
function readUser(json) {
let user = JSON.parse(json);
if (!user.age) {
throw new PropertyRequiredError("age");
}
if (!user.name) {
throw new PropertyRequiredError("name");
}
return user;
}
// Рабочий пример с try..catch
try {
let user = readUser('{ "age": 25 }');
} catch (err) {
if (err instanceof ValidationError) {
alert("Неверные данные: " + err.message); // Неверные данные: Нет свойства: name
alert(err.name); // PropertyRequiredError
alert(err.property); // name
} else if (err instanceof SyntaxError) {
alert("Ошибка синтаксиса JSON: " + err.message);
} else {
throw err; // неизвестная ошибка, повторно выбросит исключение
}
}@A@
Жаңы классты PropertyRequiredError
колдонуу абдан оңой: сиз болгону касиеттин атын көрсөтүшүңүз керек new PropertyRequiredError(property)
. Колдонуучу үчүн билдирүү message
конструктор тарабынан түзүлөт.
this.name
Конструктордогу касиет PropertyRequiredError
кайрадан кол менен дайындалганына көңүл буруңуз. this.name = <class name>
Ырас, ар бир класста ыңгайлаштырылган катаны дайындоо бир аз түйшүктүү . Биз өзүбүздүн "базалык" ката классыбызды түзүп, муну алдын алабыз this.name = this.constructor.name
. Анан андан мурунтан эле бардык каталарды мураска алат.
Аны чакыралы MyError
.
MyError
Бул жерде башка ыңгайлаштырылган ката класстары менен жөнөкөйлөштүрүлгөн код :
@A@class MyError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
}
}
class ValidationError extends MyError { }
class PropertyRequiredError extends ValidationError {
constructor(property) {
super("Нет свойства: " + property);
this.property = property;
}
}
// name корректное
alert( new PropertyRequiredError("field").name ); // PropertyRequiredError@A@
Ыңгайлаштырылган каталар азыр бир топ кыскараак, айрыкча конструктордогу ValidationError
саптан кутулдук ."this.name = ..."
Оролгон өзгөчөлүктөр
readUser
Жогорудагы коддогу функциянын максаты - "колдонуучунун маалыматтарын окуу". Процессте ар кандай каталар пайда болушу мүмкүн. Азыр бизде SyntaxError
жана бар ValidationError
, бирок келечекте функция readUser
кеңейип, башка каталарды жаратышы мүмкүн.
Чакырган код readUser
бул каталарды чечиши керек.
Учурда ал классты текшерип, белгилүү каталарды иштетип, белгисиздерди жайылтуучу if
блоктук текшерүүлөрдү колдонот. catch
Бирок эгер функция readUser
бир нече типтеги каталарды жаратса, анда биз өзүбүзгө суроо беришибиз керек: биз чындап эле коддун ал деп аталган бардык жерлеринде каталардын бардык түрлөрүн бир-бирден текшергибиз келеби readUser
?
Көбүнчө жооп "Жок" болуп саналат: тышкы код баарынан бир деңгээлде болгусу келет. Ал кандайдыр бир жалпыланган маалыматтарды окуу катасын каалайт. Эмне үчүн дал ушундай болгондугу көп учурда тиешеси жок (ката билдирүүсүндө бул айтылган). Же андан да жакшыраак, эгер ката жөнүндө маалымат алуу үчүн бир жол бар болсо, бирок ал бизге керек болсо гана.
Келгиле, ReadError
мындай каталарды көрсөтүү үчүн жаңы класс түзөлү. Ичинде ката кетсе readUser
, биз аны кармап алып, аны жаратабыз ReadError
. Биз ошондой эле баштапкы катага шилтемени сактайбыз cause
. Андан кийин тышкы коддун бар-жоктугун текшерүү керек ReadError
.
Бул код катаны аныктайт жана анын төмөнкүдө ReadError
колдонулушун көрсөтөт :readUser
try..catch
@A@class ReadError extends Error {
constructor(message, cause) {
super(message);
this.cause = cause;
this.name = 'ReadError';
}
}
class ValidationError extends Error { /*...*/ }
class PropertyRequiredError extends ValidationError { /* ... */ }
function validateUser(user) {
if (!user.age) {
throw new PropertyRequiredError("age");
}
if (!user.name) {
throw new PropertyRequiredError("name");
}
}
function readUser(json) {
let user;
try {
user = JSON.parse(json);
} catch (err) {
if (err instanceof SyntaxError) {
throw new ReadError("Синтаксическая ошибка", err);
} else {
throw err;
}
}
try {
validateUser(user);
} catch (err) {
if (err instanceof ValidationError) {
throw new ReadError("Ошибка валидации", err);
} else {
throw err;
}
}
}
try {
readUser('{bad json}');
} catch (e) {
if (e instanceof ReadError) {
alert(e);
// Исходная ошибка: SyntaxError:Unexpected token b in JSON at position 1
alert("Исходная ошибка: " + e.cause);
} else {
throw e;
}
}@A@
Жогорудагы коддо readUser
ал сүрөттөлгөндөй иштейт - функция синтаксистик каталарды жана текшерүү каталарын тааныйт жана анын ордуна каталарды ыргытат ReadError
(белгисиз каталар адаттагыдай эле жөнөтүлөт).
Тышкы код гана текшерет instanceof ReadError
. Каталардын бардык мүмкүн болгон түрлөрүн тизмектеп кереги жок
Бул ыкма "өзгөчө ороо" деп аталат, анткени биз "төмөнкү деңгээлдеги өзгөчөлүктөрдү" алып, аларды "оруп" алабыз ReadError
, бул абстракттуураак жана чакыруу кодунда колдонууга оңой. Бул ыкма объектиге багытталган программалоодо кеңири колдонулат.
Бардыгы
- Биз ката класстарыбызды
Error
башка орнотулган ката класстарынан мурастай алабыз, бирок биз мүлккө кам көрүшүбүз керекname
жанаsuper
. instanceof
Ката түрүн текшерүү үчүн колдоно алабыз . Бул да мурас менен иштейт. Бирок кээде бизде үчүнчү тараптын китепканасында пайда болгон ката объектиси болот жана классты алуунун оңой жолу жок. Андан кийин ката түрүн текшерүү үчүн касиетти колдоно аласызname
.- Өзгөчө кырдаалды ороп коюу кеңири таралган ыкма: функция төмөнкү деңгээлдеги өзгөчөлүктөрдү кармап, ар кандай төмөнкү деңгээлдегилердин ордуна бир "жогорку деңгээлдеги" өзгөчөлүктү ыргытат. Кээде төмөнкү деңгээлдеги өзгөчөлүктөр жогорудагы мисалдардагыдай эле ошол объекттин касиетине айланат
err.cause
, бирок алардын кереги жок.
Tasks
FormatError
Курулган класстан мураска калган класс түзүңүз SyntaxError
.
Класс message
, name
жана касиеттерин колдоого алышы керек stack
.
Колдонуу мисалы:
@A@let err = new FormatError("ошибка форматирования");
alert( err.message ); // ошибка форматирования
alert( err.name ); // FormatError
alert( err.stack ); // stack
alert( err instanceof FormatError ); // true
alert( err instanceof SyntaxError ); // true (потому что наследует от SyntaxError)@A@