Tipo de excepción personalizada

224

¿Puedo definir tipos personalizados para excepciones definidas por el usuario en JavaScript? Si es así, ¿cómo lo haría?

Manki
fuente
3
Tener cuidado. De acuerdo con JavaScript en 10 minutos , no obtendrá un seguimiento de la pila si arroja un valor sin caja.
Janus Troelsen
exceptionsjs.com proporciona la capacidad de crear excepciones personalizadas y proporciona algunas excepciones faltantes, incluidas ArgumentException y NotImplemented de forma predeterminada.
Steven Wexler

Respuestas:

232

De 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
fuente
77
@ b.long Está en "JavaScript: The Good Parts" (gran libro de la OMI). Esta vista previa de Google Books muestra la sección: books.google.com/books?id=PXa2bby0oQ0C&pg=PA32&lpg=PA32
orip
11
Agregar un método toString hará que se vea bien en la consola javascript. sin que se muestre como: Uncaught # <Object> con toString se muestra como: Uncaught System Error: Error detectado. Por favor, póngase en contacto con el administrador del sistema.
JDC
11
Esto no te permitirá apilar rastros a menos que heredes de Error
Luke H
¿Cómo puede filtrar dentro de un bloque catch para que solo funcione con este error personalizado?
Overdrivr
@overdrivr algo así como catch (e) { if (e instanceof TypeError) { … } else { throw e; } }⦃⦄ o catch (e) { switch (e.constructor) { case TypeError: …; break; default: throw e; }⦃⦄.
Sam Boosalis
92

Debe crear una excepción personalizada que herede prototípicamente de Error. Por ejemplo:

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;

Esta es básicamente una versión simplificada de lo que desapareció publicado anteriormente con la mejora de que los seguimientos de pila funcionan en Firefox y otros navegadores. Satisface las mismas pruebas que publicó:

Uso:

throw new InvalidArgumentException();
var err = new InvalidArgumentException("Not yet...");

Y se comportará se espera:

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
fuente
80

Puede implementar sus propias excepciones y su manejo, por ejemplo, como aquí:

// 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");
    }
}

Hay otra sintaxis para detectar una excepción escrita, aunque esto no funcionará en todos los navegadores (por ejemplo, no en 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
fuente
2
El sitio web de MSN contiene esta advertencia sobre las capturas de condición: no estándar Esta característica no es estándar y no está en una pista estándar. No lo use en sitios de producción orientados a la Web: no funcionará para todos los usuarios. También puede haber grandes incompatibilidades entre implementaciones y el comportamiento puede cambiar en el futuro.
Lawrence Dol
40

Si. Puedes lanzar lo que quieras: enteros, cadenas, objetos, lo que sea. Si desea lanzar un objeto, simplemente cree un nuevo objeto, tal como lo haría en otras circunstancias, y luego tírelo. La referencia de Javascript de Mozilla tiene varios ejemplos.

Rob Kennedy
fuente
26
function MyError(message) {
 this.message = message;
}

MyError.prototype = new Error;

Esto permite el uso como ...

try {
  something();
 } catch(e) {
  if(e instanceof MyError)
   doSomethingElse();
  else if(e instanceof Error)
   andNowForSomethingCompletelyDifferent();
}
Morgan ARR Allen
fuente
¿No funcionaría este breve ejemplo exactamente de la misma manera incluso si no heredaras el prototipo de Error? No me queda claro qué te gana en este ejemplo.
EleventyOne
1
No, e instanceof Errorsería falso.
Morgan ARR Allen
En efecto. Pero como e instanceof MyErrorsería cierto, la else if(e instanceof Error)declaración nunca sería evaluada.
EleventyOne
Bien, este es solo un ejemplo de cómo funcionaría este estilo de prueba / captura. ¿Dónde else if(e instanceof Error)sería la última captura? Probablemente seguido de un simple else(que no incluí). Algo así como default:en una declaración de cambio pero para errores.
Morgan ARR Allen
15

En breve:

Opción 1: use babel-plugin-transform-builtin-extend

Opción 2: hágalo usted mismo (inspirado en esa misma 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);
  • Si está utilizando 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 el marco Classtrophobic

Explicación:

¿Por qué extender la clase Error usando ES6 y Babel es un problema?

Porque una instancia de CustomError ya no se reconoce como tal.

class CustomError extends Error {}
console.log(new CustomError('test') instanceof Error);// true
console.log(new CustomError('test') instanceof CustomError);// false

De hecho, a partir de la documentación oficial de Babel, que no se puede sacar ninguna incorporadas en el lenguaje JavaScript , como Date, Array, DOMo Error.

El problema se describe aquí:

¿Qué pasa con las otras respuestas SO?

Todas las respuestas dadas solucionan el instanceofproblema, pero pierde el error 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"}

Mientras que usando el método mencionado anteriormente, no solo soluciona el instanceofproblema sino que también mantiene el error regular console.log:

console.log(new CustomError('test'));
// output:
// Error: test
//     at CustomError (<anonymous>:2:32)
//     at <anonymous>:1:5
JBE
fuente
11

Aquí es cómo puede crear errores personalizados con un Errorcomportamiento completamente idéntico al del nativo . Esta técnica solo funciona en Chrome y node.js por ahora. Yo también no recomendaría usarlo si usted no entiende lo que hace.

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 obtenemos

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

Entonces puedes usarlo así:

var NotImplementedError = Error.createCustromConstructor('NotImplementedError');

Y use NotImplementedErrorcomo lo haría Error:

throw new NotImplementedError();
var err = new NotImplementedError();
var err = NotImplementedError('Not yet...');

Y se comportará se espera:

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!

Tenga en cuenta que eso error.stackfunciona absolutamente correcto y no incluirá la NotImplementedErrorllamada del constructor (gracias a v8Error.captureStackTrace() ).

Nota. Hay feo eval(). La única razón por la que se usa es para corregirlo err.constructor.name. Si no lo necesita, puede simplificar un poco todo.

disgustado
fuente
2
Error.apply(self, arguments)Se especifica que no funciona . Sugiero copiar el seguimiento de la pila, que es compatible con todos los navegadores.
Kornel
11

A menudo uso un enfoque con herencia prototípica. La anulación toString()le brinda la ventaja de que herramientas como Firebug registrarán la información real en lugar de[object Object] en la consola para excepciones no detectadas.

Utilizar instanceof para determinar el tipo de excepción.

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();
Matías
fuente
8

ES6

Con la nueva clase y ampliar palabras clave ahora es mucho más fácil:

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

Usa el tiro declaración de .

A JavaScript no le importa cuál es el tipo de excepción (como lo hace Java). JavaScript solo se da cuenta, hay una excepción y cuando la detecta, puede "mirar" lo que dice la excepción ".

Si tiene diferentes tipos de excepción que debe lanzar, le sugiero que use variables que contengan la cadena / objeto de la excepción, es decir, el mensaje. Cuando lo necesite, use "throw myException" y en la captura, compare la excepción capturada con myException.

Xn0vv3r
fuente
1

Vea este ejemplo en el MDN.

Si necesita definir múltiples errores (¡pruebe el código aquí !):

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;

El código se copia principalmente de: ¿Cuál es una buena manera de extender Error en JavaScript?

Peter Tseng
fuente
1

Una alternativa a la respuesta de asselin para usar con las clases de ES2015

class InvalidArgumentException extends Error {
    constructor(message) {
        super();
        Error.captureStackTrace(this, this.constructor);
        this.name = "InvalidArgumentException";
        this.message = message;
    }
}
Mr Tsjolder
fuente
1
//create error object
var error = new Object();
error.reason="some reason!";

//business function
function exception(){
    try{
        throw error;
    }catch(err){
        err.reason;
    }
}

Ahora establecemos agregar el motivo o las propiedades que queramos al objeto de error y recuperarlo. Al hacer el error más razonable.

mateen
fuente