Loading...

Колдонуучунун каталары, error кеңейтүүсү

Колдонуучунун каталары, error кеңейтүүсү

 

Биз бир нерсени иштеп чыкканда, биздин тапшырмаларыбызда ката кетириши мүмкүн болгон ар кандай нерселер үчүн өзүбүздүн ката класстарыбыз керек болот. Тармак менен иштөөдө каталар үчүн сизге керек болушу мүмкүн HttpError, маалымат базасы операциялары үчүн DbError, издөө үчүн - NotFoundErrorж.б.

Биздин мүчүлүштүктөр messagenameжана жакшыраак сыяктуу негизги касиеттерди колдоого алышы керек stack. Бирок алардын да өз касиеттери болушу мүмкүн. Мисалы, объекттер баалуулуктары бар HttpErrorкасиетке ээ болушу мүмкүн , же .statusCode404403500

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)биз ата-эне конструкторду чакырабыз. superJavaScript бизден бала конструкторду чакырууну талап кылат , андыктан бул милдеттүү. Ата-энелик конструктор орнотот 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орнотулган .SyntaxErrorJSON.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колдонулушун көрсөтөт :readUsertry..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

маанилүүлүгү: 5

FormatErrorКурулган класстан мураска калган класс түзүңүз SyntaxError.

Класс messagenameжана касиеттерин колдоого алышы керек 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@