comprobando el tipo de error en JS

154

En JS no parece posible verificar si un argumento pasado a una función es realmente del tipo 'error' o una instancia de Error.

Por ejemplo, esto no es válido:

typeof err === 'error'

ya que solo hay 6 tipos posibles (en forma de cadenas):

El operador typeof devuelve información de tipo como una cadena. Hay seis valores posibles que typeofdevuelve:

"número", "cadena", "booleano", "objeto", "función" y "indefinido".

MSDN

Pero qué pasa si tengo un caso de uso simple como este:

function errorHandler(err) {

    if (typeof err === 'error') {
        throw err;
    }
    else {
        console.error('Unexpectedly, no error was passed to error handler. But here is the message:',err);
    }
}

Entonces, ¿cuál es la mejor manera de determinar si un argumento es una instancia de Error?

¿Es el instanceofoperador de alguna ayuda?

Alexander Mills
fuente
10
Sí, useerr instanceof Error
colecmc
2
@colecmc ¿no será problemático si el error puede provenir del código en un marco u otra ventana? En ese caso, habrá diferentes objetos de error prototipo, y la instancia de no funcionará como se esperaba. (ver developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… )
Doin
@ Doin, no lo creo. Solo validará el objeto de error real.
colecmc
1
@colecmc, creo que encontrará que si arrojó err(digamos) un iframe, pero luego lo pasa a la ventana principal para que lo entregue, obtendrá (err instanceof Error) === false. Esto se debe a que el iframe y su ventana principal tienen Errorprototipos de objetos distintos . Del mismo modo, un objeto como obj = {};will, cuando se pasa a una función que se ejecuta en una ventana diferente, yeild (obj instanceof Object)===false. (Incluso peor, en IE, si mantiene una referencia a obj después de que su ventana se destruye o navega, ¡tratar de llamar a los prototipos de objetos como obj.hasOwnProperty()arrojará errores!)
Doin
¿Cómo se comprueba el tipo de error? es decir, catch((err)=>{if (err === ENOENT)devuelve ENOENT is not definederror?
oldboy

Respuestas:

262

Puede usar el instanceofoperador (¡pero vea la advertencia a continuación!).

var myError = new Error('foo');
myError instanceof Error // true
var myString = "Whatever";
myString instanceof Error // false

Lo anterior no funcionará si el error se arrojó en una ventana / marco / iframe diferente de donde está ocurriendo la verificación. En ese caso, el instanceof Errorcheque devolverá falso, incluso para un Errorobjeto. En ese caso, el enfoque más fácil es escribir el pato.

if (myError && myError.stack && myError.message) {
  // it's an error, probably
}

Sin embargo, la escritura de pato puede producir falsos positivos si tiene objetos que contienen errores stacky messagepropiedades.

Trott
fuente
1
@ AurélienRibon Funciona bien con una ReferenceErrorinstancia. Estás comprobando el ReferenceErrorobjeto global . Prueba esto: se try { foo } catch (e) { console.log(e instanceof Error) }registra true. Si está tratando de verificar el ReferenceErrorobjeto global por alguna razón, puede usarError.isPrototypeOf(ReferenceError)
Trott
1
@ AurélienRibon (Uh, también, sí, no estaba seguro si estabas diciendo "Esto no funcionará con un ReferenceError" que es incorrecto, o si simplemente estabas señalando "Esto no funcionará con el objeto global ReferenceError" lo cual es absolutamente correcto, aunque estoy teniendo dificultades para encontrar una situación en la que uno esté comprobando eso, pero eso puede decir más sobre mi falta de imaginación que la validez del caso de uso).
Trott
Jaja, perdón por eso, muchachos, acabo de estar encerrado en un extraño error donde todas mis ReferenceErrorinstancias no eran instancias Error. Esto se debió a una llamada al vm.runInNewContext()nodo in, donde se redefinen todos los prototipos estándar. Todos mis resultados no fueron instancias de objetos estándar. Estaba buscando en SO sobre esto, y caí en este hilo :)
Aurelien Ribon
1
Puedes probarconst error = (err instanceof Error || err instanceof TypeError) ? err : new Error(err.message ? err.message : err);
Aamir Afridi
¿Cómo se verifica si es un instanceoftipo específico de error?
oldboy
25

Hice la pregunta original: la respuesta de @ Trott es seguramente la mejor.

Sin embargo, con JS como un lenguaje dinámico y con tantos entornos de tiempo de ejecución de JS, el instanceofoperador puede fallar especialmente en el desarrollo front-end cuando cruza límites como iframes. Ver: https://github.com/mrdoob/three.js/issues/5886

Si está de acuerdo con la escritura del pato, esto debería ser bueno:

let isError = function(e){
 return e && e.stack && e.message;
}

Personalmente, prefiero los idiomas con escritura estática, pero si está utilizando un lenguaje dinámico, es mejor adoptar un lenguaje dinámico como lo que es, en lugar de forzarlo a comportarse como un lenguaje con escritura estática.

si quisieras ser un poco más preciso, podrías hacer esto:

   let isError = function(e){
     return e && e.stack && e.message && typeof e.stack === 'string' 
            && typeof e.message === 'string';
    }
Alexander Mills
fuente
3
Prefiero esta versión cuando escribo una función de utilidad o biblioteca. No restringe a los usuarios a un contexto global particular y también maneja adecuadamente los errores deserializados de JSON.
Snickle
gracias, sí, en muchos casos, si parece un error, podemos tratarlo como un error. en algunos casos, la tipificación del pato es totalmente aceptable, en algunos casos no es aceptable en absoluto, en este caso, perfectamente bien.
Alexander Mills
Hice un poco de depuración usando un depurador, y la pila y el mensaje realmente parecen ser las únicas dos propiedades no nativas en instancias de Error.
Alexander Mills
1
@Trott, es posible que le interese saber que el instanceofoperador falla en algunos casos, consulte el artículo vinculado.
Alexander Mills
1
Esto es en realidad la más sencilla por lo que funciona con los diferentes tipos de errores (es decir Error, ReferenceError, ...). Y en mi entorno, el instanceoffalla miserablemente en muchas circunstancias.
Alexis Wilke
10
var myError = new Error('foo');
myError instanceof Error // true
var myString = "Whatever";
myString instanceof Error // false

El único problema con esto es

myError instanceof Object // true

Una alternativa a esto sería usar la propiedad del constructor.

myError.constructor === Object // false
myError.constructor === String // false
myError.constructor === Boolean // false
myError.constructor === Symbol // false
myError.constructor === Function // false
myError.constructor === Error // true

Aunque debe tenerse en cuenta que esta coincidencia es muy específica, por ejemplo:

myError.constructor === TypeError // false
Tom
fuente
2

Puede usar Object.prototype.toStringpara verificar fácilmente si un objeto es un Error, que también funcionará para diferentes marcos.

function isError(obj){
    return Object.prototype.toString.call(obj) === "[object Error]";
}

function isError(obj){
    return Object.prototype.toString.call(obj) === "[object Error]";
}
console.log("Error:", isError(new Error));
console.log("RangeError:", isError(new RangeError));
console.log("SyntaxError:", isError(new SyntaxError));
console.log("Object:", isError({}));
console.log("Array:", isError([]));

hev1
fuente
1

Puede usar obj.constructor.name para verificar la "clase" de un objeto https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name#Function_names_in_classes

Por ejemplo

var error = new Error("ValidationError");
console.log(error.constructor.name);

La línea anterior registrará "Error", que es el nombre de la clase del objeto. Esto podría usarse con cualquier clase en javascript, si la clase no usa una propiedad que se llame "nombre"

persistent_poltergeist
fuente
Esto no es confiable cuando se minimiza el código y se usan subclases de error
felixfbecker
1

Gracias @Trott por su código, acabo de usar el mismo código y lo agregué con un ejemplo de trabajo en tiempo real para beneficio de otros.

<html>
<body >

<p>The **instanceof** operator returns true if the specified object is an instance of the specified object.</p>



<script>
	var myError = new Error("TypeError: Cannot set property 'innerHTML' of null"); // error type when element is not defined
	myError instanceof Error // true
	
	
	
	
	function test(){
	
	var v1 = document.getElementById("myid").innerHTML ="zunu"; // to change with this
	
	try {
		  var v1 = document.getElementById("myidd").innerHTML ="zunu"; // exception caught
		  } 
		  
	catch (e) {
		  if (e instanceof Error) {
			console.error(e.name + ': ' + e.message) // error will be displayed at browser console
		  }
  }
  finally{
		var v1 = document.getElementById("myid").innerHTML ="Text Changed to Zunu"; // finally innerHTML changed to this.
	}
	
	}
	
</script>
<p id="myid">This text will change</p>
<input type="button" onclick="test();">
</body>
</html>

Zabi Baig
fuente
0

O use esto para diferentes tipos de errores

function isError(val) {
  return (!!val && typeof val === 'object')
    && ((Object.prototype.toString.call(val) === '[object Error]')
      || (typeof val.message === 'string' && typeof val.name === 'string'))
}
Chris
fuente