Я хочу кинути деякі речі в свій JS-код, і я хочу, щоб вони були instanceof Error, але я також хочу, щоб вони були чимось іншим.
У Python, як правило, можна було б підкласи Виняток.
Що потрібно зробити в JS?
Я хочу кинути деякі речі в свій JS-код, і я хочу, щоб вони були instanceof Error, але я також хочу, щоб вони були чимось іншим.
У Python, як правило, можна було б підкласи Виняток.
Що потрібно зробити в JS?
Відповіді:
Єдине стандартне поле Error об’єкт - це message
властивість. (Див. MDN або специфікацію мови EcmaScript, розділ 15.11) Все інше залежить від платформи.
Mosts середовища встановлюють stack
властивість, але fileName
іlineNumber
практично не приносять користі для використання в спадщині.
Отже, мінімалістичний підхід такий:
function MyError(message) {
this.name = 'MyError';
this.message = message;
this.stack = (new Error()).stack;
}
MyError.prototype = new Error; // <-- remove this if you do not
// want MyError to be instanceof Error
Ви можете нюхати стек, знімати з нього небажані елементи та витягувати інформацію, наприклад, fileName та lineNumber, але для цього потрібна інформація про платформу, на якій зараз працює JavaScript. У більшості випадків це непотрібно - і ви можете це зробити в післясмертовому періоді, якщо дуже хочете.
Сафарі - помітний виняток. Немає stack
властивості, але throw
ключове слово встановлює sourceURL
іline
властивості предмета, який викидається. Ці речі гарантовано є правильними.
Тестові приклади, які я використав, можна знайти тут: Порівняння власних JavaScript об’єктів Помилка .
function MyError(message) { this.message = message; this.stack = Error().stack; } MyError.prototype = Object.create(Error.prototype); MyError.prototype.name = "MyError";
MyError.prototype.constructor = MyError
.
this
, правда?
У ES6:
class MyError extends Error {
constructor(message) {
super(message);
this.name = 'MyError';
}
}
var supportsClasses = false; try {eval('class X{}'); supportsClasses = true;} catch (e) {}
this.name = this.constructor.name;
замість цього.
Коротко:
Якщо ви використовуєте ES6 без транспіляторів :
class CustomError extends Error { /* ... */}
Якщо ви використовуєте транспілятор Babel :
Варіант 1: використовувати babel-плагін-перетворення-вбудований-розширення
Варіант 2: зробіть це самостійно (надихнувшись із тієї самої бібліотеки)
function CustomError(...args) {
const instance = Reflect.construct(Error, args);
Reflect.setPrototypeOf(instance, Reflect.getPrototypeOf(this));
return instance;
}
CustomError.prototype = Object.create(Error.prototype, {
constructor: {
value: Error,
enumerable: false,
writable: true,
configurable: true
}
});
Reflect.setPrototypeOf(CustomError, Error);
Якщо ви використовуєте чистий ES5 :
function CustomError(message, fileName, lineNumber) {
var instance = new Error(message, fileName, lineNumber);
Object.setPrototypeOf(instance, Object.getPrototypeOf(this));
return instance;
}
CustomError.prototype = Object.create(Error.prototype, {
constructor: {
value: Error,
enumerable: false,
writable: true,
configurable: true
}
});
if (Object.setPrototypeOf){
Object.setPrototypeOf(CustomError, Error);
} else {
CustomError.__proto__ = Error;
}
Альтернатива: використовувати кластерофобні рамки
Пояснення:
Чому розширення класу помилок за допомогою ES6 та Babel є проблемою?
Тому що екземпляр CustomError більше не визнається таким.
class CustomError extends Error {}
console.log(new CustomError('test') instanceof Error);// true
console.log(new CustomError('test') instanceof CustomError);// false
Справді, з офіційної документації вавилонського, ви не можете розширити будь вбудовані класи JavaScript , такі як Date
, Array
, DOM
або Error
.
Проблема описана тут:
Що з іншими відповідями SO?
Усі наведені відповіді вирішують instanceof
проблему, але ви втрачаєте регулярну помилку console.log
:
console.log(new CustomError('test'));
// output:
// CustomError {name: "MyError", message: "test", stack: "Error↵ at CustomError (<anonymous>:4:19)↵ at <anonymous>:1:5"}
Хоча, використовуючи зазначений вище метод, ви не тільки виправляєте instanceof
проблему, але й зберігаєте регулярну помилку console.log
:
console.log(new CustomError('test'));
// output:
// Error: test
// at CustomError (<anonymous>:2:32)
// at <anonymous>:1:5
class CustomError extends Error { /* ... */}
неправильно обробляє специфічні для постачальника аргументи ( lineNumber
тощо), "Розширення помилки в Javascript за допомогою синтаксису ES6" є специфічним для Babel, ваше рішення ES5 використовує, const
і воно не обробляє власні аргументи.
console.log(new CustomError('test') instanceof CustomError);// false
була справжньою на момент написання, але зараз вона була вирішена. Насправді проблема, пов’язана з відповіддю , була вирішена, і ми можемо перевірити правильність поведінки тут , вставивши код у REPL і побачивши, як він отримує правильну трансляцію для інстанції з правильним прототипом ланцюга.
Редагувати: Прочитайте коментарі. Виявляється, це добре працює лише у V8 (Chrome / Node.JS). Моя мета полягала у створенні крос-браузерного рішення, яке б працювало у всіх браузерах, і забезпечувало слід стека там, де є підтримка.
Редагувати: Я створив цю спільноту Wiki, щоб дозволити більше редагування.
Рішення для V8 (Chrome / Node.JS), працює в Firefox, і його можна модифікувати, щоб він працював здебільшого правильно в IE. (див. кінець повідомлення)
function UserError(message) {
this.constructor.prototype.__proto__ = Error.prototype // Make this an instanceof Error.
Error.call(this) // Does not seem necessary. Perhaps remove this line?
Error.captureStackTrace(this, this.constructor) // Creates the this.stack getter
this.name = this.constructor.name; // Used to cause messages like "UserError: message" instead of the default "Error: message"
this.message = message; // Used to set the message
}
Оригінальна публікація на тему "Покажіть мені код!"
Коротка версія:
function UserError(message) {
this.constructor.prototype.__proto__ = Error.prototype
Error.captureStackTrace(this, this.constructor)
this.name = this.constructor.name
this.message = message
}
Я зберігаю this.constructor.prototype.__proto__ = Error.prototype
всередині функції зберегти весь код разом. Але ви також можете замінити this.constructor
з UserError
, що дозволяє переміщати код за межами функції, тому він тільки викликається один раз.
Якщо ви йдете цим маршрутом, то переконайтеся, що ви зателефонували на цю лінію ще до того, як ви кинете перший раз UserError
.
Цей застереження не застосовує цю функцію, оскільки функції створюються спочатку, незалежно від порядку. Таким чином, ви можете перемістити функцію до кінця файлу, без проблем.
Сумісність браузера
Працює в Firefox та Chrome (і Node.JS) і виконує всі обіцянки.
Internet Explorer виходить з ладу в наступному
Помилки не повинні err.stack
починатися з цього, так що "це не моя вина".
Error.captureStackTrace(this, this.constructor)
не існує, тому вам потрібно зробити щось подібне
if(Error.captureStackTrace) // AKA if not IE
Error.captureStackTrace(this, this.constructor)
toString
перестає існувати, коли ви підклас Error
. Тож вам також потрібно додати.
else
this.toString = function () { return this.name + ': ' + this.message }
IE не розглядатиме , UserError
щоб бути , instanceof Error
якщо ви не виконаєте наступне деякий час перед вамиthrow UserError
UserError.prototype = Error.prototype
Error.call(this)
насправді нічого не робить, оскільки повертає помилку, а не модифікує this
.
UserError.prototype = Error.prototype
вводить в оману. Це не робить спадкування, це робить їх одним класом .
Object.setPrototypeOf(this.constructor.prototype, Error.prototype)
, що віддають перевагу this.constructor.prototype.__proto__ = Error.prototype
, принаймні, для поточних браузерів.
Щоб уникнути помилки котла для кожного типу помилок, я поєднав розумність деяких рішень у createErrorType
функцію:
function createErrorType(name, init) {
function E(message) {
if (!Error.captureStackTrace)
this.stack = (new Error()).stack;
else
Error.captureStackTrace(this, this.constructor);
this.message = message;
init && init.apply(this, arguments);
}
E.prototype = new Error();
E.prototype.name = name;
E.prototype.constructor = E;
return E;
}
Тоді ви можете легко визначити нові типи помилок таким чином:
var NameError = createErrorType('NameError', function (name, invalidChar) {
this.message = 'The name ' + name + ' may not contain ' + invalidChar;
});
var UnboundError = createErrorType('UnboundError', function (variableName) {
this.message = 'Variable ' + variableName + ' is not bound';
});
this.name = name;
?
name
вже встановлено прототип, більше не потрібно. Я її зняв. Дякую!
У 2018 році я думаю, що це найкращий спосіб; що підтримує IE9 + та сучасні браузери.
ОНОВЛЕННЯ : Дивіться цей тест і репо для порівняння для різних реалізацій.
function CustomError(message) {
Object.defineProperty(this, 'name', {
enumerable: false,
writable: false,
value: 'CustomError'
});
Object.defineProperty(this, 'message', {
enumerable: false,
writable: true,
value: message
});
if (Error.hasOwnProperty('captureStackTrace')) { // V8
Error.captureStackTrace(this, CustomError);
} else {
Object.defineProperty(this, 'stack', {
enumerable: false,
writable: false,
value: (new Error(message)).stack
});
}
}
if (typeof Object.setPrototypeOf === 'function') {
Object.setPrototypeOf(CustomError.prototype, Error.prototype);
} else {
CustomError.prototype = Object.create(Error.prototype, {
constructor: { value: CustomError }
});
}
Також майте на увазі, що __proto__
властивість застаріла, що широко використовується в інших відповідях.
setPrototypeOf()
? Принаймні, згідно з MDN, звичайно не рекомендується використовувати його, якщо ви можете виконати те саме, просто встановивши.prototype
властивість на конструкторі (як ви робите в else
блоці для переглядів, у яких немає setPrototypeOf
).
setPrototypeOf
. Але якщо вам все-таки це потрібно (як запитує ОП), слід використовувати вбудовану методологію. Як вказує MDN, це вважається правильним способом встановлення прототипу об'єкта. Іншими словами, MDN каже, що не змінюйте прототип (оскільки це впливає на продуктивність та оптимізацію), але якщо вам доведеться, використовуйте setPrototypeOf
.
CustomError.prototype = Object.create(Error.prototype)
). Крім того, Object.setPrototypeOf(CustomError, Error.prototype)
це встановлення прототипу самого конструктора, а не визначення прототипу для нових екземплярівCustomError
. Так чи інакше, у 2016 році я думаю, що насправді є кращий спосіб розширити помилки, хоча я все-таки з'ясовую, як їх використовувати разом з Babel: github.com/loganfsmyth/babel-plugin-transform-builtin-extend/…
CustomError.prototype = Object.create(Error.prototype)
також змінюється прототип. Це потрібно змінити, оскільки в ES5 немає вбудованої логіки розширення / успадкування. Я впевнений, що плагін, який ви згадуєте, робить подібні речі.
Object.setPrototypeOf
тут не має сенсу, принаймні, не в тому, як ви його використовуєте: gist.github.com/mbrowne/4af54767dcb3d529648f5a8aa11d6348 . Можливо, ти мав намір писати Object.setPrototypeOf(CustomError.prototype, Error.prototype)
- це мало б трохи більше сенсу (хоча все ще не дасть користі від просто налаштуванняCustomError.prototype
).
Для повноти - просто тому, що жодна з попередніх відповідей не згадувала цей метод - якщо ви працюєте з Node.js і вам не потрібно дбати про сумісність браузера, бажаний ефект досягти досить легко завдяки вбудованому inherits
з util
модуля ( офіційні документи тут ).
Наприклад, припустимо, ви хочете створити спеціальний клас помилок, який приймає код помилки як перший аргумент, а повідомлення про помилку - як другий аргумент:
файл custom-error.js :
'use strict';
var util = require('util');
function CustomError(code, message) {
Error.captureStackTrace(this, CustomError);
this.name = CustomError.name;
this.code = code;
this.message = message;
}
util.inherits(CustomError, Error);
module.exports = CustomError;
Тепер ви можете створити копію та передати / кинути своє CustomError
:
var CustomError = require('./path/to/custom-error');
// pass as the first argument to your callback
callback(new CustomError(404, 'Not found!'));
// or, if you are working with try/catch, throw it
throw new CustomError(500, 'Server Error!');
Зауважте, що за допомогою цього фрагмента слід стека матиме правильне ім'я файлу та рядок, а примірник помилки матиме правильне ім'я!
Це відбувається завдяки використанню captureStackTrace
методу, який створює stack
властивість на цільовому об'єкті (у цьому випадку CustomError
інстанціюється). Детальніше про те, як це працює, перегляньте тут документацію .
this.message = this.message;
це неправильно чи все ще є шалені речі, яких я не знаю про JS?
Високоголосна відповідь Кресент Фреш вводить в оману. Хоча його попередження є недійсним, є й інші обмеження, до яких він не звертається.
По-перше, міркування в абзаці "Північ:" Півмісяця не мають сенсу. З цього пояснення випливає, що кодування "купок if (помилка instanceof MyError) else ..." є якимось обтяжливим або багатослівним порівняно з декількома заявами вилову. Кілька виразів заявки в одному блоці виловлення настільки ж стислі, як і кілька заяв на вилов - чистий і стислий код без жодних хитрощів. Це чудовий спосіб імітувати велику обробку помилок, характерних для підкидання Java.
WRT "з'являється властивість повідомлення підкласу не встановлюється", це не так, якщо ви використовуєте правильно сконструйований підклас помилок. Щоб створити власний підклас ErrorX Error, просто скопіюйте блок коду, починаючи з "var MyError =", змінивши одне слово "MyError" на "ErrorX". (Якщо ви хочете додати спеціальні методи до свого підкласу, дотримуйтесь тексту зразка).
Справжнє і суттєве обмеження підкласифікації помилок JavaScript полягає в тому, що для реалізацій JavaScript або відладчиків, які відстежують і повідомляють про стеження стека та місцезнаходження імплантації, як FireFox, розташування у вашій власній реалізації підкласу помилок буде записано як точку інстанції клас, тоді як якщо ви використовували пряму помилку, це було б місце, де ви запустили "нову помилку (...)"). Користувачі IE, ймовірно, ніколи не помітять, але користувачі Fire Bug на FF побачать марні значення імен файлу та значення рядка, повідомлені поряд із цими Помилками, і їм доведеться детально простежувати стек до елемента №1, щоб знайти реальне місцезнаходження інстанції.
Crescent Fresh's
видалено!
Як щодо цього рішення?
Замість того, щоб кидати власну помилку, використовуючи:
throw new MyError("Oops!");
Ви обгорнете об'єкт Error (на зразок декоратора):
throw new MyError(Error("Oops!"));
Це гарантує правильність всіх атрибутів, таких як стек, fileName lineNumber та ін.
Все, що вам потрібно зробити, це або скопіювати атрибути, або визначити для них getters. Ось приклад використання геттерів (IE9):
function MyError(wrapped)
{
this.wrapped = wrapped;
this.wrapped.name = 'MyError';
}
function wrap(attr)
{
Object.defineProperty(MyError.prototype, attr, {
get: function()
{
return this.wrapped[attr];
}
});
}
MyError.prototype = Object.create(Error.prototype);
MyError.prototype.constructor = MyError;
wrap('name');
wrap('message');
wrap('stack');
wrap('fileName');
wrap('lineNumber');
wrap('columnNumber');
MyError.prototype.toString = function()
{
return this.wrapped.toString();
};
new MyErr (arg1, arg2, new Error())
і в конструкторі MyErr ми використовуємо Object.assign
для присвоєння властивостей останнього аргументуthis
Як кажуть деякі люди, з ES6 це досить просто:
class CustomError extends Error { }
Тому я спробував це в моєму додатку (Angular, Typescript), і це просто не вийшло. Через деякий час я виявив, що проблема виходить із Typescript: O
Дивіться https://github.com/Microsoft/TypeScript/isissue/13965
Це дуже тривожно, тому що якщо ви робите:
class CustomError extends Error {}
try {
throw new CustomError()
} catch(e) {
if (e instanceof CustomError) {
console.log('Custom error');
} else {
console.log('Basic error');
}
}
У вузлі або безпосередньо у вашому браузері відобразиться: Custom error
Спробуйте запустити це з Typescript у вашому проекті на майданчику Typescript, воно відобразиться Basic error
...
Рішення полягає в наступному:
class CustomError extends Error {
// we have to do the following because of: https://github.com/Microsoft/TypeScript/issues/13965
// otherwise we cannot use instanceof later to catch a given type
public __proto__: Error;
constructor(message?: string) {
const trueProto = new.target.prototype;
super(message);
this.__proto__ = trueProto;
}
}
Моє рішення простіше, ніж інші надані відповіді, і не має недоліків.
Він зберігає ланцюжок прототипу Error та всі властивості на Error, не потребуючи конкретних знань про них. Він протестований у Chrome, Firefox, Node та IE11.
Єдине обмеження - це додатковий запис у верхній частині стеку викликів. Але це легко ігнорувати.
Ось приклад з двома спеціальними параметрами:
function CustomError(message, param1, param2) {
var err = new Error(message);
Object.setPrototypeOf(err, CustomError.prototype);
err.param1 = param1;
err.param2 = param2;
return err;
}
CustomError.prototype = Object.create(
Error.prototype,
{name: {value: 'CustomError', enumerable: false}}
);
Приклад використання:
try {
throw new CustomError('Something Unexpected Happened!', 1234, 'neat');
} catch (ex) {
console.log(ex.name); //CustomError
console.log(ex.message); //Something Unexpected Happened!
console.log(ex.param1); //1234
console.log(ex.param2); //neat
console.log(ex.stack); //stacktrace
console.log(ex instanceof Error); //true
console.log(ex instanceof CustomError); //true
}
Для середовищ, для яких потрібен поліфіл setPrototypeOf:
Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
obj.__proto__ = proto;
return obj;
};
throw CustomError('err')
замістьthrow new CustomError('err')
У наведеному вище прикладі Error.apply
(також Error.call
) нічого не робиться для мене (Firefox 3.6 / Chrome 5). Я вирішую такий спосіб:
function MyError(message, fileName, lineNumber) {
var err = new Error();
if (err.stack) {
// remove one stack level:
if (typeof(Components) != 'undefined') {
// Mozilla:
this.stack = err.stack.substring(err.stack.indexOf('\n')+1);
}
else if (typeof(chrome) != 'undefined' || typeof(process) != 'undefined') {
// Google Chrome/Node.js:
this.stack = err.stack.replace(/\n[^\n]*/,'');
}
else {
this.stack = err.stack;
}
}
this.message = message === undefined ? err.message : message;
this.fileName = fileName === undefined ? err.fileName : fileName;
this.lineNumber = lineNumber === undefined ? err.lineNumber : lineNumber;
}
MyError.prototype = new Error();
MyError.prototype.constructor = MyError;
MyError.prototype.name = 'MyError';
У "Вузлі", як інші говорили, це просто:
class DumbError extends Error {
constructor(foo = 'bar', ...params) {
super(...params);
if (Error.captureStackTrace) {
Error.captureStackTrace(this, DumbError);
}
this.name = 'DumbError';
this.foo = foo;
this.date = new Date();
}
}
try {
let x = 3;
if (x < 10) {
throw new DumbError();
}
} catch (error) {
console.log(error);
}
Я просто хочу додати те, що вже заявили інші:
Щоб переконатися, що користувальницький клас помилок відображається належним чином у сліді стека, потрібно встановити властивість імені прототипу класу помилок у властивості імені користувацького класу помилок. Це те, що я маю на увазі:
CustomError.prototype = Error.prototype;
CustomError.prototype.name = 'CustomError';
Отже, повним прикладом може бути:
var CustomError = function(message) {
var err = new Error(message);
err.name = 'CustomError';
this.name = err.name;
this.message = err.message;
//check if there is a stack property supported in browser
if (err.stack) {
this.stack = err.stack;
}
//we should define how our toString function works as this will be used internally
//by the browser's stack trace generation function
this.toString = function() {
return this.name + ': ' + this.message;
};
};
CustomError.prototype = new Error();
CustomError.prototype.name = 'CustomError';
Коли все буде сказано і зроблено, ви кидаєте новий виняток, і це виглядає приблизно так (я ліниво спробував це в інструментах хромованого розробника):
CustomError: Stuff Happened. GASP!
at Error.CustomError (<anonymous>:3:19)
at <anonymous>:2:7
at Object.InjectedScript._evaluateOn (<anonymous>:603:39)
at Object.InjectedScript._evaluateAndWrap (<anonymous>:562:52)
at Object.InjectedScript.evaluate (<anonymous>:481:21)
Мої 2 копійки:
а) Через те, що за доступ до Error.stack
власності (як і в деяких відповідях) передбачено велику штрафну ефективність.
б) Тому що це лише один рядок.
c) Тому що рішення https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error , схоже, не зберігає інформацію про стек.
//MyError class constructor
function MyError(msg){
this.__proto__.__proto__ = Error.apply(null, arguments);
};
приклад використання
http://jsfiddle.net/luciotato/xXyeB/
this.__proto__.__proto__
є MyError.prototype.__proto__
, тому він встановлює __proto__
ЗА ВСІХ ІНСТАНЦІЙ MyError конкретну новостворену помилку. Він зберігає властивості та методи класу MyError, а також додає нові властивості помилок (включаючи .stack) у __proto__
ланцюг.
Ви не можете мати більше ніж один екземпляр MyError з корисною інформацією про стек.
Не використовуйте це рішення, якщо ви не повністю розумієте, що this.__proto__.__proto__=
робить.
Оскільки винятки JavaScript важко підклас, я не підклас. Я просто створюю новий клас винятків і використовую помилку всередині нього. Я змінюю властивість Error.name так, щоб він виглядав як мій спеціальний виняток на консолі:
var InvalidInputError = function(message) {
var error = new Error(message);
error.name = 'InvalidInputError';
return error;
};
Згаданий вище виняток можна викинути так само, як звичайну помилку, і вона буде працювати, як очікувалося, наприклад:
throw new InvalidInputError("Input must be a string");
// Output: Uncaught InvalidInputError: Input must be a string
Caveat: слід стека не є ідеальним, оскільки він приведе вас туди, де створена нова помилка, а не куди ви кинете. Це не велика справа в Chrome, оскільки він забезпечує вам повний слід стека безпосередньо в консолі. Але це більш проблематично, наприклад, у Firefox.
m = new InvalidInputError(); dontThrowMeYet(m);
m = new ...
то Promise.reject(m)
. Це не обов'язково, але код читається простіше.
Як було зазначено у відповіді Мохсена, в ES6 можна розширити помилки за допомогою класів. Це набагато простіше, і їх поведінка більше відповідає власним помилкам ... але, на жаль, використовувати це в браузері непросто, якщо вам потрібно підтримувати браузери до ES6. Дивіться нижче кілька приміток про те, як це можна реалізувати, але тим часом я пропоную порівняно простий підхід, який включає деякі найкращі пропозиції з інших відповідей:
function CustomError(message) {
//This is for future compatibility with the ES6 version, which
//would display a similar message if invoked without the
//`new` operator.
if (!(this instanceof CustomError)) {
throw new TypeError("Constructor 'CustomError' cannot be invoked without 'new'");
}
this.message = message;
//Stack trace in V8
if (Error.captureStackTrace) {
Error.captureStackTrace(this, CustomError);
}
else this.stack = (new Error).stack;
}
CustomError.prototype = Object.create(Error.prototype);
CustomError.prototype.name = 'CustomError';
У ES6 це так просто, як:
class CustomError extends Error {}
... і ви можете виявити підтримку класів ES6 за допомогою try {eval('class X{}')
, але ви отримаєте синтаксичну помилку, якщо спробуєте включити версію ES6 до сценарію, завантаженого старими браузерами. Тому єдиним способом підтримки всіх браузерів буде динамічне завантаження окремого сценарію (наприклад, через AJAX або eval()
) для браузерів, що підтримують ES6. Наступним ускладненням є те, що eval()
він не підтримується у всіх середовищах (через політику безпеки вмісту), що може бути, а може і не враховувати ваш проект.
Тож наразі, або перший підхід вище, або просто використання Error
безпосередньо, не намагаючись розширити, здається, найкраще, що практично можна зробити для коду, який потребує підтримки браузерів, які не є ES6.
Є ще один підхід, який деякі люди можуть захотіти врахувати, а саме - використовувати Object.setPrototypeOf()
там, де це можливо, для створення об’єкта помилки, який є екземпляром вашого користувальницького типу помилок, але який виглядає і поводиться більше як нативну помилку в консолі (завдяки відповіді Бена для рекомендації). Ось мій погляд на такий підхід: https://gist.github.com/mbrowne/fe45db61cea7858d11be933a998926a8 . Але з огляду на те, що одного разу ми зможемо просто використовувати ES6, особисто я не впевнений, що складність цього підходу варта.
Спосіб зробити це правильно - повернути результат застосування від конструктора, а також встановити прототип звичайним складним способом javascripty:
function MyError() {
var tmp = Error.apply(this, arguments);
tmp.name = this.name = 'MyError'
this.stack = tmp.stack
this.message = tmp.message
return this
}
var IntermediateInheritor = function() {}
IntermediateInheritor.prototype = Error.prototype;
MyError.prototype = new IntermediateInheritor()
var myError = new MyError("message");
console.log("The message is: '"+myError.message+"'") // The message is: 'message'
console.log(myError instanceof Error) // true
console.log(myError instanceof MyError) // true
console.log(myError.toString()) // MyError: message
console.log(myError.stack) // MyError: message \n
// <stack trace ...>
Єдині проблеми з цим способом зробити це в даний момент (я це трохи повторив), це те
stack
та message
не включаються до складуMyError
таПерша проблема може бути виправлена шляхом ітерації через усі неперелічені властивості помилки, використовуючи трюк у цій відповіді: Чи можливо отримати не перелічені успадковані імена властивостей об’єкта? , але це не підтримується, тобто <9. Другу проблему можна було б вирішити, відірвавши цей рядок у сліді стека, але я не впевнений, як безпечно це зробити (можливо, просто видаливши другий рядок e.stack.toString () ??).
Фрагмент показує все це.
function add(x, y) {
if (x && y) {
return x + y;
} else {
/**
*
* the error thrown will be instanceof Error class and InvalidArgsError also
*/
throw new InvalidArgsError();
// throw new Invalid_Args_Error();
}
}
// Declare custom error using using Class
class Invalid_Args_Error extends Error {
constructor() {
super("Invalid arguments");
Error.captureStackTrace(this);
}
}
// Declare custom error using Function
function InvalidArgsError(message) {
this.message = `Invalid arguments`;
Error.captureStackTrace(this);
}
// does the same magic as extends keyword
Object.setPrototypeOf(InvalidArgsError.prototype, Error.prototype);
try{
add(2)
}catch(e){
// true
if(e instanceof Error){
console.log(e)
}
// true
if(e instanceof InvalidArgsError){
console.log(e)
}
}
Я б зробив крок назад і подумав, чому ти хочеш це зробити? Я думаю, що справа в тому, щоб по-різному поводитися з різними помилками.
Наприклад, у Python ви можете обмежити оператор вилову лише ловом MyValidationError
, і, можливо, ви хочете зробити щось подібне в JavaScript.
catch (MyValidationError e) {
....
}
Ви не можете зробити це в JavaScript. Там буде лише один блок ловлі. Ви повинні використовувати оператор if про помилку для визначення її типу.
catch(e) {
if(isMyValidationError(e)) {
...
} else {
// maybe rethrow?
throw e;
}
}
Я думаю, що замість цього я б кинув необроблений об'єкт із типом, повідомленням та будь-якими іншими властивостями, які вважаєте за потрібне.
throw { type: "validation", message: "Invalid timestamp" }
І коли ви виявите помилку:
catch(e) {
if(e.type === "validation") {
// handle error
}
// re-throw, or whatever else
}
error.stack
, стандартні інструменти не працюватимуть з ним і т. Д. Кращим способом було б додати властивості до екземпляра помилки, наприкладvar e = new Error(); e.type = "validation"; ...
Це ґрунтується на відповіді Джорджа Бейлі , але розширює та спрощує оригінальну ідею. Він написаний на CoffeeScript, але легко перетворити на JavaScript. Ідея полягає у розширенні власної помилки Бейлі за допомогою декоратора, який завершує її, що дозволяє легко створювати нові власні помилки.
Примітка. Це працюватиме лише у V8. Немає підтримки Error.captureStackTrace
в інших середовищах.
Декоратор приймає ім’я для типу помилки і повертає функцію, яка приймає повідомлення про помилку, і додає ім'я помилки.
CoreError = (@message) ->
@constructor.prototype.__proto__ = Error.prototype
Error.captureStackTrace @, @constructor
@name = @constructor.name
BaseError = (type) ->
(message) -> new CoreError "#{ type }Error: #{ message }"
Зараз легко створювати нові типи помилок.
StorageError = BaseError "Storage"
SignatureError = BaseError "Signature"
Для розваги ви тепер можете визначити функцію, яка кидає a, SignatureError
якщо вона викликається із занадто великою кількістю аргументів.
f = -> throw SignatureError "too many args" if arguments.length
Це було випробувано досить добре і, здається, прекрасно працює на V8, підтримуючи прослідкування, позицію тощо.
Примітка. Використання new
необов’язкового під час створення власної помилки.
якщо ви не переймаєтесь виставами на помилки, це найменша можливість зробити
Object.setPrototypeOf(MyError.prototype, Error.prototype)
function MyError(message) {
const error = new Error(message)
Object.setPrototypeOf(error, MyError.prototype);
return error
}
ви можете використовувати його без нового просто MyError (повідомлення)
Змінюючи прототип після виклику помилки конструктора, нам не потрібно встановлювати стік викликів і повідомлення
У ES6 у Mohsen є чудова відповідь, яка задає ім'я, але якщо ви використовуєте TypeScript або якщо ви живете в майбутньому, де, сподіваюся, цю пропозицію для полів загального та приватного класу просунулась минулого етапу 3 як пропозиція і зробила її переходьте на етап 4 як частину ECMAScript / JavaScript, то, можливо, ви хочете знати, що це лише трохи коротше. На етапі 3 браузери починають реалізовувати функції, тож якщо ваш браузер підтримує його, код нижче може просто працювати. (Випробувано в новому браузері Edge v81, здається, він працює добре). Будьте попереджені, хоча ця функція є нестабільною на даний момент, і її слід використовувати обережно, і ви завжди повинні перевіряти підтримку браузера на нестабільні функції. Ця публікація в основному призначена для тих майбутніх мешканців, коли веб-переглядачі можуть це підтримувати. Щоб перевірити перевірку підтримкия можу використовувати . В даний час у нього є 66% підтримка на ринку браузерів, який є там, але це не так здорово, тому якщо ви дійсно хочете використовувати його зараз і не хочете чекати або використовувати транспілятор, як Babel, або щось на зразок TypeScript . MDNі
class EOFError extends Error {
name="EOFError"
}
throw new EOFError("Oops errored");
Порівняйте це з безіменною помилкою, яка при закиданні не запише це ім'я.
class NamelessEOFError extends Error {}
throw new NamelessEOFError("Oops errored");
this.name = 'MyError'
зовнішню частину функції та змінити її наMyError.prototype.name = 'MyError'
.