Manejo de errores en JavaScript
Aprende como evitar el 💥¡CRASH!💥 en tus programas, controla los posibles errores en tu código.
Aprende como evitar el 💥¡CRASH!💥 en tus programas, controla los posibles errores en tu código.
try...catch...
define un bloque de expresiones para intentar ejecutar y otro bloque para agregar la forma en la que vamos a responder si se presenta un error.
Alguna vez hemos llegado a ver algo así:
const inicio = 1810;
console.log('La lucha de independencia duró :' + (fin - inicio) + ' años.');
iniciarBatalla();
Con solo un vistazo sabemos que eso va a crashear.
Como nosotros no especificamos que hacer en caso de que se presente un error en esas líneas de código, el propio lenguaje se encarga de procesarlo y el navegador de mostrarlo, pero con eso se ha detenido la ejecución del programa. Lo que significa que las siguientes líneas de código ya no se ejecutarán. (adios a la ejecución de la función iniciarBatalla
)
Vamos a ver la estructura del try...catch...
try {
// Código a ejecutar
} catch (e) {
// En caso de que alguna línea falle, el flujo pasará a este lugar
}
Así de sencillo es proteger nuestros programas y poder tener siempre el control del flujo.
try {
const inicio = 1810;
console.log('La lucha de independencia duró :' + (fin - inicio) + ' años.');
} catch (e) {
console.log('Ups. Ha ocurrido un error al imprimir el valor');
}
Con el código anterior ahora no sale el molesto mensaje de error del navegador, ahora tomamos el error y mostramos en consola un mensaje sobre él. Tal vez te preguntes que tiene la variable e
dentro del bloque catch
pues vamos a ver...
La variable e
nos da información referente al error que ha ocurrido. Normalmente, es un objeto que tiene la propiedad name
y la propiedad message
, con el nombre del tipo de error y el mensaje de error, respectivamente. 👁️ ¡Ojo! Esta variable solo podrá ser utilizada en el bloque catch
Entonces veamos el código siguiente...
try {
const inicio = 1810;
console.log('La lucha de independencia duró :' + (fin - inicio) + ' años.');
} catch (e) {
console.log(e.name); // ReferenceError
console.log(e.message); // fin is not defined
}
Podemos ver que efectivamente el objeto e
tiene esas 2 propiedades que dependiendo la situación nos servirán para reaccionar de una o de otra forma al error ocurrido.
try {
const inicio = 1810;
console.log('La lucha de independencia duró :' + (fin - inicio) + ' años.');
} catch ({name, message}) {
console.log(name); // ReferenceError
console.log(message); // fin is not defined
}
En el código anterior vemos el mismo ejemplo pero ahora desestructurando el objeto e
para obtener las propiedades y evitar utilizar e.name
o e.message
. 👁️ ¡Ojo! La variable name
y message
estarán reservadas dentro del bloque catch
entonces no podremos tener otras variables con esos mismos nombres dentro del bloque.
Vamos a ver un poco más sobre el bloque catch
...
try {
const inicio = 1910;
if (inicio !== 1810) {
throw 'Hay un error en la fecha inicial';
}
} catch (e) {
console.log(e); // Hay un error en la fecha inicial
console.log(e.name); // undefined
console.log(e.message); // undefined
}
¿Qué paso aquí? Como vemos en el código anterior, nosotros mismo lanzamos el error con la instrucción throw
y solo mandamos una cadena de texto, esto ya no es un objeto Error
, por lo tanto, dentro del bloque catch
la variable e
es un simple string
y no tiene ni la propiedad name
ni message
.
try {
throw {
name: 'Nombre de error propio',
message: 'Ha ocurrido un error',
time: new Date().getTime()
};
} catch (e) {
console.log(e);
}
En el código anterior vemos que así como podíamos provocar el error y pasar solo una cadena de texto, también podemos pasar un objeto personalizado, en este caso pasamos el tiempo en que ocurrió el error. (es solo un ejemplo para mostrar que se puede enviar un objeto con cualquier propiedad)
Vamos a ver un bloque más que acompaña a try...catch... y es finally...
El bloque finally
tendrá las instrucciones que queramos que sean ejecutadas una vez concluidos el bloque try
y/o el bloque catch
.
try {
throw new Error('Ha ocurrido un error');
} catch (e) {
console.log(e.message);
} finally {
console.log('Me ejecutaré ocurra o un error');
}
// SALIDA
// Ha ocurrido un error
// Me ejecutaré ocurra o un error
El bloque finally
nos puede ser útil, por ejemplo, si tenemos una petición al servidor, podríamos querer mostrar una ventana de carga para indicar que se está cargando información, y claro está que una vez obtenidos los datos del servidor querremos dejar de mostrar la ventana de carga.
function mostrarInformacion() {
try {
mostrarVentanaCarga('Cargando batallas ...');
obtenerBatallas();
ocultarVentanaCarga();
} catch (e) {
console.log(e);
}
}
mostrarInformacion();
Pero tal vez también queramos quitar la ventana de carga si es que hubo un error, para poder en ese caso mostrar el mensaje indicando el error ocurrido. Podríamos hacer algo así...
function mostrarInformacion() {
try {
mostrarVentanaCarga('Cargando batallas ...');
obtenerBatallas();
ocultarVentanaCarga();
} catch (e) {
console.log(e);
ocultarVentanaCarga();
}
}
mostrarInformacion();
Pero podríamos mejor utilizar el bloque finally
y así, independientemente de si la ejecución falla o no, siempre cerraremos la ventana de carga. Es un ejemplo sencillo, pero bueno para entender como funciona el bloque finally
.
function mostrarInformacion() {
try {
mostrarVentanaCarga('Cargando batallas ...');
obtenerBatallas();
} catch (e) {
console.log(e);
} finally {
ocultarVentanaCarga();
}
}
mostrarInformacion();
Ahora veremos que pasa con bloques try...catch...finally anidados.
try {
// try exterior
throw new Error('Ha ocurrido un error en el try externo');
try {
// try interior
throw new Error('Ha ocurrido un error en el try interno');
} catch(e) {
// catch interior
console.log('interno: ' + e.message);
}
} catch (e) {
// catch exterior
console.log('externo: ' + e.message);
}
// SALIDA:
// externo: Ha ocurrido un error en el try externo
En el código de arriba podemos ver directamente el resultado. Lo que pasa es que una vez dentro del try
interno se lanza un error con throw
, entonces 'busca' si existe un bloque catch
que pueda tomar ese error y actuar sobre él, y en este caso claro que lo encuentra y es su bloque catch
el que tiene la palabra 'externo'.
try {
// try exterior
try {
// try interior
throw new Error('Ha ocurrido un error en el try interno');
} catch(e) {
// catch interior
console.log('interno: ' + e.message);
}
} catch (e) {
// catch exterior
console.log('externo: ' + e.message);
}
// SALIDA:
// interno: Ha ocurrido un error en el try interno
Ahora en el ejemplo de arriba el try
exterior ya no es quien lanza el error sino el try
interior y pasa lo mismo, 'busca' si existe el bloque catch
para que tome el error, lo encuentra y ya vemos lo que pasa. Pero que tal con el siguiente código...
try {
// try exterior
try {
// try interior
throw new Error('Ha ocurrido un error en el try interno');
} finally {
console.log('Finally interno');
}
} catch (e) {
// catch exterior
console.log('externo:', e.message);
}
// SALIDA:
// Finally interno
// externo:' 'Ha ocurrido un error en el try interno
¿Qué ha pasado? Fácil, el try
interno 'busca' a su bloque catch
, al no encontrarlo el error 'viaja' al catch
exterior que es quien toma el error y lo muestra. Claro está que si no se encuentra un bloque catch
, no habrá quien tome el error para actuar en consecuencia.
👁️ ¡Ojo! Puedes haber notado que se agregó el bloque finally
al try
interno, esto es porque un bloque try
forzosamente debe estar acompañado de un bloque catch
o un bloque finally
o marcará un error de sintaxis.
Hay algo más que contar sobre el bloque finally
y es lo que pasa cuando se usa la instrucción return
dentro del bloque.
(function() {
})();
Para el ejemplo vamos a crear una función auto ejecutada, no te preocupes si no entiendes esto, quédate por ahora con la idea de que es una función que se llama de forma automática, es importante trabajar con una función para este ejemplo porque usaremos la palabra clave return
.
(function() {
try {
throw new Error('Lanzamos un error');
} catch(e) {
console.log('Obtenemos el error: ' + e.message);
} finally {
console.log('Se finaliza');
return 'soy un valor retornado desde finally';
}
})();
// SALIDA:
// Obtenemos el error: Lanzamos un error
// Se finaliza
// soy un valor retornado desde finally
El valor que retorne el bloque finally
se convierte en el valor de retorno de toda la estructura try...catch...finally...
, no importa que haya un return
dentro del bloque try
o dentro del bloque catch
, si existe un return
en el bloque finally
se tomará solo ese, veámoslo en un ejemplo...
(function() {
try {
throw new Error('Lanzamos un error');
} catch(e) {
console.log('Obtenemos el error: ' + e.message);
return 'Soy un valor retornado desde catch';
} finally {
console.log('Se finaliza');
return 'soy un valor retornado desde finally';
}
})();
// SALIDA:
// Obtenemos el error: Lanzamos un error
// Se finaliza
// soy un valor retornado desde finally
Obtenemos exactamente el mismo resultado, porque se ignoró el return
dentro del bloque catch
y se tomo el que está dentro del bloque finally
.
(function () {
try {
// try exterior
try {
// try interior
throw new Error('Lanzamos un error');
} catch (e) {
// catch interior
console.log('Catch interior: ' + e.message);
return 'Soy un valor retornado desde catch interior';
} finally {
// finally interior
return 'soy un valor retornado desde finally interior';
}
} catch (e) {
// catch exterior
console.log('Catch exterior: ' + e.message);
return 'Soy un valor retornado desde catch exterior';
} finally {
// finally exterior
console.log('Finally exterior');
}
})();
// SALIDA:
// Catch interior: Lanzamos un error
// Finally exterior
// soy un valor retornado desde finally interior
En el ejemplo podemos ver que en el bloque finally
interno se ejecuta un return
, como se terminó de ejecutar la estructura try...catch...finally...
interior, el valor retornado se queda en espera de que la estructura try...catch...finally...
exterior se termine de ejecutar y cuando eso pasa es que llega el turno delreturn
del finally
interior y se muestra en consola. Es por eso que se muestra primero el mensaje 'Finally exterior' que el valor retornado por el finally
interior.
(function () {
try {
// try exterior
try {
// try interior
throw new Error('Lanzamos un error');
} catch (e) {
// catch interior
console.log('Catch interior: ' + e.message);
return 'Soy un valor retornado desde catch interior';
} finally {
// finally interior
console.log('Finally interior');
return 'soy un valor retornado desde finally interior';
}
} catch (e) {
// catch exterior
console.log('Catch exterior: ' + e.message);
return 'Soy un valor retornado desde catch exterior';
} finally {
// finally exterior
console.log('Finally exterior');
return 'soy un valor retornado desde finally exterior';
}
})();
// SALIDA:
// Catch interior: Lanzamos un error
// Finally interior
// Finally exterior
// soy un valor retornado desde finally exterior
Pero en este último ejemplo sucede algo similiar, con la unica diferencia que al haber un return
en el bloque finally
exterior es el que se retornará, ignorando el return
del bloque finally
interior.
Es importante proteger el flujo de nuestros programas para poder reaccionar a los siempre inoportunos errores, una cosa es que ocurra un error y otra es que ese error nos rompa el programa. Debemos capturar los errores, procesarlos y no dejar que el usuario sea interrumpido o que su experiencia sea desastrosa. Espero que este post te sea un poco de utilidad.
Krative Digital Boletin informativo
Únase al boletín para recibir las últimas actualizaciones en su bandeja de entrada.