Tipo de exceção personalizada

224

Posso definir tipos personalizados para exceções definidas pelo usuário em JavaScript? Se sim, como eu faria isso?

Manki
fonte
3
Cuidado. De acordo com o JavaScript em 10 minutos, você não receberá um rastreamento de pilha se lançar um valor sem caixa.
Janus Troelsen
exceptionsjs.com fornece a capacidade de criar exceções personalizadas e fornece algumas exceções ausentes, incluindo ArgumentException e NotImplemented por padrão.
Steven Wexler

Respostas:

232

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:[email protected]\">system administrator</a>.",
  toString:    function(){return this.name + ": " + this.message;} 
}; 
jon077
fonte
7
@ b.long Está em "JavaScript: The Good Parts" (ótimo livro IMO). Esta visualização do Google Livros mostra a seção: books.google.com/books?id=PXa2bby0oQ0C&pg=PA32&lpg=PA32
orip
11
Adicionar um método toString fará com que ele seja bem exibido no console javascript. sem ele mostra como: Uncaught # <Object> com o toString como: Uncaught System Error: Erro detectado. Por favor contacte o administrador do sistema.
JDC 16/08
11
Isso não vai permitir que você empilhar traços menos que você herdar de erro
Luke H
Como você pode filtrar dentro de um bloco catch para trabalhar apenas com esse erro personalizado?
Overdrivr
@overdrivr algo como catch (e) { if (e instanceof TypeError) { … } else { throw e; } }⦃⦄ ou catch (e) { switch (e.constructor) { case TypeError: …; break; default: throw e; }⦃⦄.
sam boosalis 5/04
92

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!
asselin
fonte
80

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");
}
Sergey Ilinsky
fonte
2
O site do MSN carrega este aviso sobre capturas de condições: Não padrão Este recurso não é padrão e não está em uma faixa de padrões. Não o use em sites de produção voltados para a Web: ele não funcionará para todos os usuários. Também pode haver grandes incompatibilidades entre implementações e o comportamento pode mudar no futuro.
Lawrence Dol
40

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.

Rob Kennedy
fonte
26
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();
}
Morgan ARR Allen
fonte
Este breve exemplo não funcionaria exatamente da mesma maneira, mesmo que você não herdasse o protótipo do Error? Não está claro para mim o que você ganha neste exemplo.
EleventyOne 27/07
1
Não, e instanceof Errorseria falso.
Morgan ARR Allen
De fato. Mas, como e instanceof MyErrorseria verdade, a else if(e instanceof Error)afirmação nunca seria avaliada.
EleventyOne 31/07
Certo, este é apenas um exemplo de como esse estilo de tentativa / captura funcionaria. Onde 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.
Morgan ARR Allen
15

Em resumo:

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, DOMou Error.

O problema está descrito aqui:

E as outras respostas do SO?

Todas as respostas fornecidas corrigem o instanceofproblema, 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 instanceofproblema, 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
JBE
fonte
11

Aqui está como você pode criar erros personalizados com o mesmo Errorcomportamento 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 NotImplementedErrorcomo 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.stackfunciona absolutamente correto e não inclui a NotImplementedErrorchamada 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.

desfigurado
fonte
2
Error.apply(self, arguments)está especificado para não funcionar . Sugiro copiar o rastreamento de pilha, que é compatível com vários navegadores.
Kornel
11

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 instanceofpara determinar o tipo de exceção.

main.js

// 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

Exception.js

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;
};

DuplicateIdException.js

ns.DuplicateIdException = function(message) {
    this.name = 'Duplicate ID';
    this.message = message;
};

ns.DuplicateIdException.prototype = new ns.Exception();
Matthias
fonte
8

ES6

Com a nova classe e estender as palavras-chave, agora é muito mais fácil:

class CustomError extends Error {
  constructor(message) {
    super(message);
    //something
  }
}
Brunno
fonte
7

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.

Xn0vv3r
fonte
1

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?

Peter Tseng
fonte
1

Uma alternativa à resposta do asselin para uso nas classes ES2015

class InvalidArgumentException extends Error {
    constructor(message) {
        super();
        Error.captureStackTrace(this, this.constructor);
        this.name = "InvalidArgumentException";
        this.message = message;
    }
}
Tsjolder
fonte
1
//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.

mateen
fonte