Posso definir tipos personalizados para exceções definidas pelo usuário em JavaScript? Se sim, como eu faria isso?
Posso definir tipos personalizados para exceções definidas pelo usuário em JavaScript? Se sim, como eu faria isso?
Respostas:
Do 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:sysadmin@acme-widgets.com\">system administrator</a>.",
toString: function(){return this.name + ": " + this.message;}
};
catch (e) { if (e instanceof TypeError) { … } else { throw e; } }
⦃⦄ ou catch (e) { switch (e.constructor) { case TypeError: …; break; default: throw e; }
⦃⦄.
Você deve criar uma exceção personalizada que herda prototipicamente de Error. Por exemplo:
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;
Essa é basicamente uma versão simplificada do que foi desatualizado, postado acima, com o aprimoramento que os rastreamentos de pilha funcionam no Firefox e em outros navegadores. Satisfaz os mesmos testes que ele postou:
Uso:
throw new InvalidArgumentException();
var err = new InvalidArgumentException("Not yet...");
E se comportará é esperado:
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!
Você pode implementar suas próprias exceções e seu tratamento, por exemplo, como aqui:
// 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");
}
}
Há outra sintaxe para capturar uma exceção digitada, embora isso não funcione em todos os navegadores (por exemplo, não no 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");
}
Sim. Você pode jogar o que quiser: números inteiros, seqüências de caracteres, objetos, o que for. Se você deseja lançar um objeto, simplesmente crie um novo objeto, da mesma forma que criaria um sob outras circunstâncias e, em seguida, jogue-o. A referência Javascript do Mozilla tem vários exemplos.
function MyError(message) {
this.message = message;
}
MyError.prototype = new Error;
Isso permite o uso como ..
try {
something();
} catch(e) {
if(e instanceof MyError)
doSomethingElse();
else if(e instanceof Error)
andNowForSomethingCompletelyDifferent();
}
e instanceof Error
seria falso.
e instanceof MyError
seria verdade, a else if(e instanceof Error)
afirmação nunca seria avaliada.
else if(e instanceof Error)
seria a última captura. Provavelmente seguido por um simples else
(que eu não incluí). Mais ou menos como default:
na instrução switch, mas por erros.
Em resumo:
Se você estiver usando o ES6 sem transpilers :
class CustomError extends Error { /* ... */}
Consulte Estendendo o erro em Javascript com sintaxe ES6 para saber qual é a melhor prática atual
Se você estiver usando o transpiler Babel :
Opção 1: use babel-plugin-transform-builtin-extend
Opção 2: faça você mesmo (inspirado nessa mesma biblioteca)
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);
Se você estiver usando o ES5 puro :
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;
}
Alternativa: use estrutura Classtrophobic
Explicação:
Por que estender a classe Error usando ES6 e Babel é um problema?
Porque uma instância do CustomError não é mais reconhecida como tal.
class CustomError extends Error {}
console.log(new CustomError('test') instanceof Error);// true
console.log(new CustomError('test') instanceof CustomError);// false
De fato, a partir da documentação oficial de Babel, você não pode estender qualquer built-in classes JavaScript , como Date
, Array
, DOM
ou Error
.
O problema está descrito aqui:
E as outras respostas do SO?
Todas as respostas fornecidas corrigem o instanceof
problema, mas você perde o erro regular console.log
:
console.log(new CustomError('test'));
// output:
// CustomError {name: "MyError", message: "test", stack: "Error↵ at CustomError (<anonymous>:4:19)↵ at <anonymous>:1:5"}
Considerando que, usando o método mencionado acima, você não apenas corrige o instanceof
problema, mas também mantém o erro regular console.log
:
console.log(new CustomError('test'));
// output:
// Error: test
// at CustomError (<anonymous>:2:32)
// at <anonymous>:1:5
Aqui está como você pode criar erros personalizados com o mesmo Error
comportamento do nativo . Essa técnica funciona apenas no Chrome e node.js por enquanto. Eu também não recomendaria usá-lo se você não entender o que ele faz.
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;
}
})();
Como resultado, obtemos
/**
* 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)
Então você pode usá-lo assim:
var NotImplementedError = Error.createCustromConstructor('NotImplementedError');
E use NotImplementedError
como faria Error
:
throw new NotImplementedError();
var err = new NotImplementedError();
var err = NotImplementedError('Not yet...');
E se comportará é esperado:
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!
Observe que isso error.stack
funciona absolutamente correto e não inclui a NotImplementedError
chamada do construtor (graças às v8 Error.captureStackTrace()
).
Nota. Há feio eval()
. A única razão pela qual é usada é a correção err.constructor.name
. Se você não precisar, pode simplificar um pouco tudo.
Error.apply(self, arguments)
está especificado para não funcionar . Sugiro copiar o rastreamento de pilha, que é compatível com vários navegadores.
Costumo usar uma abordagem com herança prototípica. A substituição toString()
oferece a vantagem de que ferramentas como o Firebug registram as informações reais em vez de [object Object]
no console para exceções não capturadas.
Use instanceof
para determinar o tipo de exceção.
// 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();
Use a instrução throw .
JavaScript não se importa com o tipo de exceção (como Java). O JavaScript apenas percebe, há uma exceção e, quando você o captura, pode "olhar" o que a exceção "diz".
Se você tiver diferentes tipos de exceção, sugiro usar variáveis que contenham a string / objeto da exceção, ou seja, a mensagem. Onde você precisar, use "throw myException" e, na captura, compare a exceção capturada com myException.
Veja este exemplo no MDN.
Se você precisar definir vários erros (teste o código aqui !):
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;
O código é principalmente copiado de: Qual é uma boa maneira de estender o Erro em JavaScript?
//create error object
var error = new Object();
error.reason="some reason!";
//business function
function exception(){
try{
throw error;
}catch(err){
err.reason;
}
}
Agora, definimos adicionar o motivo ou quaisquer propriedades que desejamos ao objeto de erro e recuperá-lo. Tornando o erro mais razoável.