Могу ли я определить пользовательские типы для определяемых пользователем исключений в JavaScript? Если да, то как мне это сделать?



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Используйте оператор бросать.
JavaScript не заботится о типе исключения (как это делает Java). JavaScript просто замечает, что есть исключение, и когда вы его поймаете, вы можете «посмотреть», что «говорит» исключение.
Если у вас есть разные типы исключений, которые вы должны генерировать, я бы предложил использовать переменные, которые содержат строку / объект исключения, то есть сообщение. Там, где вам это нужно, используйте "throw myException" и в catch сравните пойманное исключение с myException.
Да. Вы можете бросать все, что хотите: целые числа, строки, объекты, что угодно. Если вы хотите выбросить объект, просто создайте новый объект, как если бы вы создавали его при других обстоятельствах, а затем выбросите его. Справочник Mozilla по Javascript имеет несколько примеров.
Вы можете реализовать свои собственные исключения и их обработку, например, как здесь:
// define exceptions "classes"
function NotNumberException() {}
function NotPositiveNumberException() {}
// try some code
try {
// some function/code that can throw
if (isNaN(value))
throw new NotNumberException();
else
if (value < 0)
throw new NotPositiveNumberException();
}
catch (e) {
if (e instanceof NotNumberException) {
alert("not a number");
}
else
if (e instanceof NotPositiveNumberException) {
alert("not a positive number");
}
}
Существует другой синтаксис для перехвата типизированного исключения, хотя он не будет работать в каждом браузере (например, не в IE):
// define exceptions "classes"
function NotNumberException() {}
function NotPositiveNumberException() {}
// try some code
try {
// some function/code that can throw
if (isNaN(value))
throw new NotNumberException();
else
if (value < 0)
throw new NotPositiveNumberException();
}
catch (e if e instanceof NotNumberException) {
alert("not a number");
}
catch (e if e instanceof NotPositiveNumberException) {
alert("not a positive number");
}
На веб-сайте MSN есть предупреждение об обнаружении условий: Нестандартный Эта функция нестандартна и не входит в стандартную дорожку. Не используйте его на рабочих сайтах, выходящих в Интернет: он подойдет не для всех пользователей. Между реализациями также может быть большая несовместимость, и поведение может измениться в будущем.
От WebReference:
throw {
name: "System Error",
level: "Show Stopper",
message: "Error detected. Please contact the system administrator.",
htmlMessage: "Error detected. Please contact the <a href=\"mailto:[email protected]\">system administrator</a>.",
toString: function(){return this.name + ": " + this.message;}
};
@ b.long Это в "JavaScript: Хорошие части" (отличная книга IMO). В предварительном просмотре Google Книг показан раздел: books.google.com/books?id=PXa2bby0oQ0C&pg=PA32&lpg=PA32
Добавление метода toString заставит его красиво отображаться в консоли javascript. без него отображается как: Uncaught # <Object> с toString он отображается как: Неперехваченная системная ошибка: обнаружена ошибка. Пожалуйста свяжитесь с системным администратором.
Это не позволит вам складывать трассировки, если вы не унаследуете от Error
Как можно отфильтровать в блоке catch, чтобы работать только с этой настраиваемой ошибкой?
@overdrivr что-то вроде ⦃catch (e) { if (e instanceof TypeError) { … } else { throw e; } }⦄ или ⦃catch (e) { switch (e.constructor) { case TypeError: …; break; default: throw e; }⦄.
@samboosalis, это работает, если вы выбрасываете TypeError, что здесь не так (и даже если бы это было так, я хочу иметь возможность фильтровать именно эту ошибку и не улавливать другие ошибки, созданные вручную).
function MyError(message) {
this.message = message;
}
MyError.prototype = new Error;
Это позволяет использовать как ..
try {
something();
} catch(e) {
if (e instanceof MyError)
doSomethingElse();
else if (e instanceof Error)
andNowForSomethingCompletelyDifferent();
}
Разве этот краткий пример не работал бы точно так же, даже если бы вы не унаследовали прототип Error? Мне не ясно, что это дает вам в этом примере.
Нет, e instanceof Error будет ложным.
Действительно. Но поскольку e instanceof MyError был бы правдой, оператор else if (e instanceof Error) никогда не будет оцениваться.
Да, это всего лишь пример того, как будет работать этот стиль try / catch. Где else if (e instanceof Error) был бы последним уловом. Скорее всего, последует простой else (который я не включил). Вроде как default: в операторе switch, но с ошибками.
Вот как вы можете создавать собственные ошибки с полностью идентичным поведению собственного Error. Это техника работает только в Chrome и node.js на данный момент. Я также не рекомендую использовать это, если вы не понимаете, что он делает.
Error.createCustromConstructor = (function() {
function define(obj, prop, value) {
Object.defineProperty(obj, prop, {
value: value,
configurable: true,
enumerable: false,
writable: true
});
}
return function(name, init, proto) {
var CustomError;
proto = proto || {};
function build(message) {
var self = this instanceof CustomError
? this
: Object.create(CustomError.prototype);
Error.apply(self, arguments);
Error.captureStackTrace(self, CustomError);
if (message != undefined) {
define(self, 'message', String(message));
}
define(self, 'arguments', undefined);
define(self, 'type', undefined);
if (typeof init == 'function') {
init.apply(self, arguments);
}
return self;
}
eval('CustomError = function ' + name + '() {' +
'return build.apply(this, arguments); }');
CustomError.prototype = Object.create(Error.prototype);
define(CustomError.prototype, 'constructor', CustomError);
for (var key in proto) {
define(CustomError.prototype, key, proto[key]);
}
Object.defineProperty(CustomError.prototype, 'name', { value: name });
return CustomError;
}
})();
В результате получаем
/**
* name The name of the constructor name
* init User-defined initialization function
* proto It's enumerable members will be added to
* prototype of created constructor
**/
Error.createCustromConstructor = function(name, init, proto)
Тогда вы можете использовать это так:
var NotImplementedError = Error.createCustromConstructor('NotImplementedError');
И используйте NotImplementedError так же, как и Error:
throw new NotImplementedError();
var err = new NotImplementedError();
var err = NotImplementedError('Not yet...');
И он будет вести себя ожидаемо:
err instanceof NotImplementedError // -> true
err instanceof Error // -> true
NotImplementedError.prototype.isPrototypeOf(err) // -> true
Error.prototype.isPrototypeOf(err) // -> true
err.constructor.name // -> NotImplementedError
err.name // -> NotImplementedError
err.message // -> Not yet...
err.toString() // -> NotImplementedError: Not yet...
err.stack // -> works fine!
Обратите внимание, что error.stack работает абсолютно правильно и не будет включать вызов конструктора NotImplementedError (спасибо Error.captureStackTrace() v8).
Примечание. Есть некрасивый eval(). Единственная причина, по которой он используется, - это получение правильного err.constructor.name. Если он вам не нужен, можно все немного упростить.
Error.apply(self, arguments) - это указано не работать. Я предлагаю вместо этого копирование трассировки стека, который совместим с несколькими браузерами.
Я часто использую подход с прототипным наследованием. Переопределение toString() дает вам то преимущество, что такие инструменты, как Firebug, будут записывать фактическую информацию вместо [object Object] в консоль для неперехваченных исключений.
Используйте instanceof, чтобы определить тип исключения.
// just an exemplary namespace
var ns = ns || {};
// include JavaScript of the following
// source files here (e.g. by concatenation)
var someId = 42;
throw new ns.DuplicateIdException('Another item with ID ' +
someId + ' has been created');
// Firebug console:
// uncaught exception: [Duplicate ID] Another item with ID 42 has been created
ns.Exception = function() {
}
/**
* Form a string of relevant information.
*
* When providing this method, tools like Firebug show the returned
* string instead of [object Object] for uncaught exceptions.
*
* @return {String} information about the exception
*/
ns.Exception.prototype.toString = function() {
var name = this.name || 'unknown';
var message = this.message || 'no description';
return '[' + name + '] ' + message;
};
ns.DuplicateIdException = function(message) {
this.name = 'Duplicate ID';
this.message = message;
};
ns.DuplicateIdException.prototype = new ns.Exception();
//create error object
var error = new Object();
error.reason = "some reason!";
//business function
function exception(){
try{
throw error;
}catch(err){
err.reason;
}
}
Теперь мы устанавливаем добавление причины или любых других свойств к объекту ошибки и получаем его. Сделав ошибку более разумной.
Вы должны создать собственное исключение, которое прототипно наследуется от Error. Например:
function InvalidArgumentException(message) {
this.message = message;
// Use V8's native method if available, otherwise fallback
if ("captureStackTrace" in Error)
Error.captureStackTrace(this, InvalidArgumentException);
else
this.stack = (new Error()).stack;
}
InvalidArgumentException.prototype = Object.create(Error.prototype);
InvalidArgumentException.prototype.name = "InvalidArgumentException";
InvalidArgumentException.prototype.constructor = InvalidArgumentException;
По сути, это упрощенная версия того, что обескураженный опубликовало выше, с улучшением, заключающимся в том, что трассировки стека работают в Firefox и других браузерах. Он удовлетворяет тем же тестам, которые он опубликовал:
Применение:
throw new InvalidArgumentException();
var err = new InvalidArgumentException("Not yet...");
И он будет вести себя ожидаемо:
err instanceof InvalidArgumentException // -> true
err instanceof Error // -> true
InvalidArgumentException.prototype.isPrototypeOf(err) // -> true
Error.prototype.isPrototypeOf(err) // -> true
err.constructor.name // -> InvalidArgumentException
err.name // -> InvalidArgumentException
err.message // -> Not yet...
err.toString() // -> InvalidArgumentException: Not yet...
err.stack // -> works fine!
См. этот пример в MDN.
Если вам нужно определить несколько ошибок (проверьте код здесь!):
function createErrorType(name, initFunction) {
function E(message) {
this.message = message;
if (Error.captureStackTrace)
Error.captureStackTrace(this, this.constructor);
else
this.stack = (new Error()).stack;
initFunction && initFunction.apply(this, arguments);
}
E.prototype = Object.create(Error.prototype);
E.prototype.name = name;
E.prototype.constructor = E;
return E;
}
var InvalidStateError = createErrorType(
'InvalidStateError',
function (invalidState, acceptedStates) {
this.message = 'The state ' + invalidState + ' is invalid. Expected ' + acceptedStates + '.';
});
var error = new InvalidStateError('foo', 'bar or baz');
function assert(condition) { if (!condition) throw new Error(); }
assert(error.message);
assert(error instanceof InvalidStateError);
assert(error instanceof Error);
assert(error.name == 'InvalidStateError');
assert(error.stack);
error.message;
Код в основном скопирован из: Какой хороший способ расширить Error в JavaScript?
Альтернатива ответу Асселин для использования с классами ES2015
class InvalidArgumentException extends Error {
constructor(message) {
super();
Error.captureStackTrace(this, this.constructor);
this.name = "InvalidArgumentException";
this.message = message;
}
}
Коротко:
Если вы используете ES6 без транспайлеров:
class CustomError extends Error { /* ... */}
См. Расширение ошибки в Javascript с помощью синтаксиса ES6, чтобы узнать текущую передовую практику
Если вы используете Вавилонский транспилятор:
Вариант 1: используйте babel-plugin-transform-builtin-extension
Вариант 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) {
const 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;
}
Альтернатива: использовать фреймворк Кластрофобия
Объяснение:
Почему расширение класса Error с помощью ES6 и Babel является проблемой?
Поскольку экземпляр CustomError больше не распознается как таковой.
class CustomError extends Error {}
console.info(new CustomError('test') instanceof Error);// true
console.info(new CustomError('test') instanceof CustomError);// false
Фактически, из официальной документации Babel вы не может расширять какие-либо встроенные классы JavaScript, например Date, Array, DOM или Error.
Проблема описана здесь:
А как насчет других ответов SO?
Все приведенные ответы устраняют проблему instanceof, но вы теряете обычную ошибку console.info:
console.info(new CustomError('test'));
// output:
// CustomError {name: "MyError", message: "test", stack: "Error↵ at CustomError (<anonymous>:4:19)↵ at <anonymous>:1:5"}
Принимая во внимание, что, используя метод, упомянутый выше, вы не только исправляете проблему instanceof, но и сохраняете обычную ошибку console.info:
console.info(new CustomError('test'));
// output:
// Error: test
// at CustomError (<anonymous>:2:32)
// at <anonymous>:1:5
ES6
С новым классом и расширением ключевых слов стало намного проще:
class CustomError extends Error {
constructor(message) {
super(message);
//something
}
}
Остерегаться. Согласно JavaScript за 10 минут вы не получите трассировку стека, если выбросите распакованное значение.