Excepciones en Javascript

30 Ene 2011

Javascript, como otros muchos lenguajes de programación, incluye soporte nativo para el manejo de excepciones.

Las excepciones, son imprevistos que ocurren durante la ejecución de un programa; anormalidades que impiden o alteran el comportamiento o flujo normal de un software.

Su función, es separar el código para el manejo de errores de la lógica de aplicación del programa. En aquellos lenguajes que incluyen soporte para el manejo de excepciones, al producirse la anomalidad, se desciende en la pila de ejecución hasta encontrar un manejador para la excepción, el cual toma el control en ese momento de la aplicación.

En Javascript, el manejo de errores puede resultar más necesario que en otros lenguajes debido a la dificultad natural de este para testear aplicaciones. Por lo general, los errores en Javascript son crípticos y poco informativos, especialmente en Internet Explorer. Esto hace interesante el contar con operadores que nos permitan lanzar nuestros propios mensajes advirtiendo al usuario de que algo ha ido mal.

Excepciones con Throw

La forma más sencilla para lanzar errores es utilizando throw. Este comando permite enviar al navegador un evento similar al que se produce cuando ocurre algún imprevisto o nos encontramos ante un tipo inesperado de datos. El lenguaje permite enviar todo tipo de elementos, incluyendo texto, números, valores booleanos o incluso objetos. Sin embargo, la opción más usual es enviar el objeto nativo Error:

throw new Error( "Something bad happened." );

Un ejemplo típico de uso es insertarlo como estamento en un condicional:

function mySum(){
  var result = 0,
  l = arguments.length;
  if( l > 10 ) throw console.error( 'Too much arguments!' );
    for( var x = 0; x < l; x++ ){
    result += arguments[x];
  }
  return result;
}
 
console.log( mySum( 3, 4, 34, 5, 7, 8, 1, 32 ) );  // 94
console.log( mySum( 3, 4, 34, 5, 7, 8, 1, 32, 3, 5, 8 ) );  // Error: Too much arguments
// El resto del código, será ignorado tras lanzar la excepción...
// ...
// ...

La función anterior suma el valor numérico de los argumentos que le pasemos pero, si pasamos de un límite (10 en este caso), lanza una excepción abortando la ejecución.

Además de cadenas personalizadas, podemos crear un objeto de error personalizado que pasaremos a la excepción permitiéndonos conocer más detalles sobre lo ocurrido:

var err = new Error();
err.name = 'My API error';
 
function checkNumber( my_string ){
  if( parseFloat( my_string )  != my_string ){
    err.message = my_string + ' is not a number. ';
    throw ( console.dir( err ) );
  }
  console.log( my_string + ' is a number!' );
  return true;
}
 
console.log( checkNumber( 7 ) ); // 7 is a number!
console.log( checkNumber( '10' ) ); // 10 is a number!
console.log( checkNumber( 'No number' ) ); //  Firebug Object

En este caso, hemos creado una función que evalúa si una cadena tiene el formato de un número. Para ello, hemos utilizado la coerción de los tipos de datos que ofrece la comparación no-estricta ‘!=’. Cuando nos topamos con una cadena como ‘No number’, arrojamos un objeto de error con algunas trazas útiles para su rastreo:

fileName : "http://www.etnassoft.com/"
lineNumber : 87
message : "No number is not a number. "
name : "My API error"
stack : "Error()@:0\n_firebugInje.../www.etnassoft.com/:67\n"
__proto__ : Error

Es importante tener en cuenta que throw detiene completamente la ejecución del hilo actual (no sólo el ámbito o contexto del error), por lo que no el resto del código será inmediatamente ignorado. Es por ello que se considera una buena práctica el colocar el estamento throw al final del resto de evaluaciones en un condicional:

function checkNumber( my_string ){
  var result;
  if( my_string < 0 ) result = my_string + ' is a very low Number!';
  if( my_string > 1000 ) result =   my_string + ' is a very high Number!';
  if( parseFloat( my_string )  != my_string ){
    err.message = my_string + ' is not a number. ';
    throw ( console.error( my_string + ' is not a Number!' ) );
  }
  result = result || my_string + ' is a correct Number!';
  return result;
}
 
console.log( checkNumber( -17 ) ); // -17 is too low Number!
console.log( checkNumber( '10' ) ); // 10 is a correct Number!
console.log( checkNumber( 1009 ) ); // 1009 is a Number!
console.log( checkNumber( 'no number' ) ); // no number is not a Number!

Excepciones con Try / Catch

Try … Catch corresponde a un tipo de estructura de control Javascript con la que comprobar el flujo de un programa frente a comportamientos inesperados. La diferencia entre esta estructura y la anterior es que mientras throw detiene completamente la ejecución, catch realiza una acción determinada frente a los errores para proseguir después con el flujo definido.

Este tipo de excepciones se estructuran mediante un bloque de código que evalúa una condición previa y propone en consecuencia una ejecución predefinida y otra alternativa frente a anormalidades. La sintaxis es sencilla y actúa como un condicional más:

function checkPassword( my_string ){
  var msg = {};
  try {
    if( my_string.length < 6 ) throw 'SHORT';
    if( my_string.length > 10 ) throw 'LONG';
    msg.status = 'Pass Validated';
  } catch( err ) {
    if( err == 'SHORT' ) msg.status = 'Pass is too short';
    if( err == 'LONG' ) msg.status = 'Pass is too long';
  } finally {
    console.log( 'Password evaluated: ' + msg.status );
  }
}
 
checkPassword( '1234' ); // Password evaluated: Pass is too short
checkPassword( '12345678901' ); // Password evaluated: Pass is too long
checkPassword( '12345678' ); // Password evaluated: Pass Validated
 
console.log( 'The execution continues here... ' );

Como podemos comprobar, la estructura no difiere mucho de la de otros lenguajes como Java: se evalúa un argumento y en caso de no corresponderse con lo esperado, lanzamos un throw que captura la cláusula catch. Con el código de error controlado, podemos proseguir con el flujo normal de la aplicación sin interrumpirla.

Conclusión

En el desarrollo de software, el manejo de excepciones es una técnica necesaria para asegurarnos un correcto funcionamiento de nuestras aplicaciones. En el caso de Javascript, este factor tiene una mayor importancia si cabe dado que hablamos de un lenguaje donde los errores suelen aparecer de una forma silenciosa: por lo general, el usuario no se percatará (ni será advertido) de que algo ha fallado; únicamente la ejecución se detendrá sin más.

Es por esto que las excepciones, debemos entenderlas como algo más que una herramienta para el desarrollador. Para las tareas de debug, ya contamos con aplicaciones específicas muy potentes como la consola de Firebug en Firefox o suites de tests completas como jUnit. Tenemos que entender las excepciones como una metodología más que permite mejorar la experiencia de usuario y aplicar una capa más de control sobre los flujos de datos que manejamos.

Usando las estructuras de control que hemos mostrado correctamente, podemos evitar la aparición de errores incomprensibles (y bloqueantes) perimitiéndonos reconducir el flujo según lo necesitemos sin deterner el programa.

Más información

Sitepoint, Debugging JavaScript: Throw Away Your Alerts
Nicholas Zakas, The art of throwing JavaScript errors

Más:

{2} Comentarios.

  1. Zzarcon

    Hola Carlos, me gustaría saber como puedo controlar los errores que ocurren asincronamente utilizando un único try/catch, en vez de tener que estar pasandole a cada método una función tipo callbackError. Muchas gracias.

  2. Jhonatan

    Me parece muy bueno tu post, he estado buscando tutoriales sobre el tema y este es el que me ha parecido mas claro y fácil de entender.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *