RSS Feed

Manejando Errores

10

diciembre 12, 2012 by - @pjnovas

Algo necesario en todo lenguaje de programación es el manejo de errores. Lamentablemente en JS es poco usado y en llamadas asíncronas está mal usado la mayoría de las veces. Es por eso que armo este post para que nos enteremos de que el objeto Error existe en JS y nos sirve mucho mas de lo que sabemos y/o creemos.

Arrancamos con un ejemplo común:

try {
  //.. venimos haciendo cosas 
  throw "disparo un error!";
}
catch(e){
  console.log(e); // "disparo un error!"
}

Bueno, tiramos un error que es un string, pero podemos hacerlo mejor:

try {
  //.. venimos haciendo cosas 
  throw new Error("disparo un error!");
}
catch(e){
  console.log(e.message); //"disparo un error!"
  console.log(e.stack); //Error: disparo un error! at http://localhost/:3:2 at condition .... 
}

Ahh, ahora se vé mucho mejor, tenemos el stack y el mensaje por separado y si hacemos un throw e; vamos a ver el error completito en la consola (o en el terminal en el caso de NodeJS). O sea, que ahora es un objeto, no mas cadenas voladas en el eter.

Cómo vimos en otro post, el alcance de las variables es a nivel de función, pero para el catch, nuestra variable e tiene alcance SOLO dentro del catch:

console.dir(e); //error: e no está declarada
catch(e){
  console.dir(e); //jeje   
}
console.dir(e); //error: e no está declarada

Manejar errores de esta manera nos trae muchas facilidades, aparte de tener el Stack y de tener realmente una Excepcion y no un string, nos abre las puertas para empezar a manejar errores enserio, ahora podemos:

Crear nuestros Tipos de Errores:

function MiError(mensaje) {
  this.name = "MiError";
  this.message = mensaje || "No Especificado";
}
MiError.prototype = new Error();
MiError.prototype.constructor = MiError;
 
try {
  throw new MiError("explotó!");
} catch (e) {
  console.log(e.name);     // "MiError"
  console.log(e.message);  // "explotó"
  console.log(e.stack);  // Error: explotó! at http://localhost/:9:2 at condition .... 
  console.log(e instanceof MiError); //true
}

Simplemente extendemos la clase Error con nuestro nuevo tipo de error :)

Al tener tipos propios de errores, es seguro que vamos a necesitar catchear cada tipo, porque para eso los creamos, ya que esto no es un feo string y ahora es una clase, podemos comprobar por instancia para que quede prolijo:

try {
  throw new MiError("explotó!");
} catch (e) {
  if (e instanceof MiError){
    console.log('fue MiError');
  }
  else {
    console.log('fue otra cosa así que disparo un error no manejado');
    throw e;
  }
}

Van a ver por ahí casos donde se utiliza:

catch(e if e instanceof MiError)

Cuidado con eso porque solo lo soporta Mozilla y no es parte del Standard ECMAScript.

Aparte del coqueto Error (piénsenlo como la clase Exception de C# o Java, sería el error más genérico), también tenemos otras Excepciones ya definidas que heredan de Error:

  • EvalError
  • RangeError
  • ReferenceError
  • SyntaxError
  • TypeError
  • URIError

Todo este manejo de errores funciona tanto en el cliente, como en el servidor con NodeJS. Lo que hay que tener en cuenta es que algunos navegadores ancianos no soportan la clase Error, pero hablamos de navegadores muy ancianos.

Para el cliente también hay otros tipos de excepciones ya definidas por ejemplo DOMExceptions (este va a depender de los niveles de DOM y del navegador, pero va para otro post :))

Manejando errores en Callbacks

Ahora bien, tenemos otro caso de manejo de errores, los famosos y amados callbacks, como manejaríamos los errores?, tenemos un tema, no deberíamos hacer un throw ya que un callback puede ejecutarse en otro momento (asíncrono) y disparar una excepción dentro de un callback en otro tiempo puede traer graves problemas.

Lo que se utiliza, y no se bien si realmente es un standard (en NodeJS), es tener un argumento más (el primero) en cada callback, el cual va a ser el error:

dao.leerDatos(function(error, datos){
   // en error tengo la excepcion (pero no hay try/ catch)
});

Porque el argumento error en el primer lugar?, yo creo que viene por el lado del opcional, en JavaScript ningún argumento es obligatorio, podemos definirlos o no, y usarlos o no, no estamos obligados a seguir una firma de métodos/ funciones como en otros lenguajes. Entonces de esta manera si queremos agarrar el argumento datos estamos obligados a agregar el argumento error, por su orden :P

Es por eso que en el caso de un callback que no devuelve nada, simplemente se dispara cuando algo termina, lo ideal sería que igualmente tenga un argumento error:

dao.guardar(entidad, function(error){
  // etc ..
});

Bueno, y como manejo si hubo error o no?, ya que no hay try/ catch?

dao.leerDatos(function(error, datos){
  if (error) {
    // manejo el error.
    return;
  }
  
  console.dir(datos);
});

Simple!, compruebo si error es un valor verdadero y recuerden hacer un return, o salir de ese callback de alguna otra forma, ya que sino la función continuará su ejecución, y no me suena a que queremos que suceda.

Mirando esto mismo de quien llama a ese manejo podemos ver como funciona:

function hacerLlamada(termino) {
  
  dao.leerDatos(function(error, datos){
    if (error) {
      termino(error);
      return;
    }
    
    // algún calculo mágico
    termino(null, datos);
  });
}

hacerLlamada(function(err, datos){
  if (err) throw err;
  else console.dir(datos);
});

El ejemplo no es lo más feliz, pero lo que te quiero mostrar es como sería el que llama a esa función con otro callback. De está manera nos queda el código mucho mas ordenado y con los errores bien manejados, como si hubiera un try y catch (que va subiendo en su ejecución a medida que es disparado y atrapado por el que lo llamó), hacemos lo mismo con callbacks y somos todos felices :).

Links útiles y fuentes de info ;)


  • http://twitter.com/dzajdband Dan Zajdband

    Excelente.

  • matiasarriola

    “no mas cadenas voladas en el eter”. Totalmente de acuerdo!

  • Nahuel Barrios

    Copado post virgen!
    Detalle: 3er párrafo de Manejando errores en callbacks duplicas “podemos”

    • pjnovas

      Grax – arreglado ;)

  • Mariano Benedettini

    En realidad para todo lo que sea asincrónico lo mejor es tener un callback de error aparte, usualmente llamado errback. Es mucho más simple y claro, y no tenés dudas de que cuando ese callback de error es llamado es porque ocurrió un error. Y encima te evitás ese feo if (error).
    En Node “plano” se maneja un único callback donde el primer argumento es error, pero en cualquier framework decente se separan a callback y errback (hasta jQuery con su deferred lo hace).

    Otra cosa: en javascript asincrónico olvidense de throw/try/catch. El error se pasa como parámetro, jamás se hace un throw ya que hay una operación asincrónica involucrada (usualmente de IO) y por lo tanto vuelve inútil al bloque try/catch (nunca se entera del throw que hay adentro).

    slds,
    Mariano.

    • pjnovas

      Groso, estoy de acuerdo con la parte de “try y catch en asinc”, pero no con el “errback”, es algo personal jaja, me gusta la “forma nodejs”, no me atrae mucho tener 2 callbacks separados como 2 parámetros con uno para error. Pero si me gusta por ejemplo la forma de encadenamiento, como en jquery cuando usas el “.fail(errCallback)”, o utilizando un EventEmitter y haciendo “.on(‘error’, errCallback)” lo que me recuerda que no hubo post de EventEmitter!, quien se anima??? :)

      Saludos!

  • Felipe Uribe

    Hola, gracias por el articulo, llevo un buen rato buscando entender bien el concepto de los errores, try, catch y throw.
    esto es lo que he entendido.

    try: declaración le permite probar un bloque de código de errores.
    catch: declaración le permite manejar el error.
    throw: permite crear errores personalizados.

    si fuera ejecutar un código que lea un archivo y si no lo encuentra cree un error:

    fs.readFile(‘www/index2.html’,function (err, data) {
    if (err)throw new Error(“No se encontro ruta”);
    if (data){
    console.log(data.toString(‘utf8′));
    }
    );

    como realmente debo manejar el error para no detener la ejecución de código?.

    gracias por la atención prestada.

    saludos.

    • pjnovas

      Hola Felipe,

      La creación del error es instanciando la clase Error

      var miError = new Error();

      el throw lo que hace es disparar el error, lo que causa que la ejecución se detenga.

      La ejecución se detiene, ya que para el interpretador esto es un error no manejado, o inesperado, porque si no lo envolviste en un try {} catch() {}, en teoria no esperabas que hubiera un error.

      Entonces para que la ejecucion no se detenga tenes que envolverlo en un try {} catch {}, o bien en tu caso, al ser asynchronico, deberías seguir enviandolo a tu callback para que este lo maneje.

      En resumeno, si haces un “throw” y su invocador no lo está manejando va a llegar hasta el proceso y lo va a interpretar como un error Fatal de aplicación, por lo que detiene el proceso.

      Otra forma de manejar cualquier error Fatal en la aplicación para NodeJS, es usar el modulo domains (http://nodejs.org/api/domain.html), o también podés ver mas info en este post: http://fernetjs.com/2012/06/una-excepcion-y-chau-proceso/

      Saludos!

      • Felipe Uribe

        Pablo, muy agradecido por tu explicación, ahora si voy entendiendo sobre el manejo de errores.
        saludos.

  • Pingback: chicago homes for sale