El escenario lo conocemos todos. Desplegamos los nuevos cambios que acabamos de desarrollar en el frontend y al cargar la página, solo vemos una página a medio cargar. La consola nos avisa que hay un error en el componente X porque algún campo que usamos vino nulo, o sea que hay alguna verificación de datos que no contemplamos o nos olvidamos de implementar. Por supuesto que este es el caso feliz. ¿Qué pasaría si nuestro conjunto de datos de prueba no incluye esa posibilidad, y el error termina estallando cuando un cliente ingresa mal algún campo dos meses más tarde? Y si ese cliente simplemente se rinde y se dirige a la competencia, ¿cómo nos enteramos de que esto está ocurriendo? Hoy vamos a ver varias alternativas para atacar estas debilidades.
Con ustedes... los errores
Vue nos presenta dos tipos de errores. Los primeros se llaman warnings o advertencias, y nos aparecen en la consola con el título [Vue warn].
Los segundos son los errores propiamente dichos, cuya leyenda puede variar según el tipo de error del que se trate. Normalmente van acompañados de una advertencia.
Técnicas para el manejo de errores en Vue
Las técnicas de las que disponemos hoy para hacer manejo de errores en Vue son las siguientes:
- errorHandler
- warnHandler
- renderError
- errorCaptured
- window.onerror (si bien es una técnica de JS puro, se puede integrar bien a nuestra app de Vue)
Comencemos.
La función errorHandler
Esta función es un controlador de errores genérico. La podemos implementar de la siguiente manera:
Vue.config.errorHandler = function(err, vm, info) {
}
Los tres parámetros que recibe son:
- err: el objeto de error propiamente dicho,
- vm: nuestra aplicación de Vue,
- info: el mensaje de error (String).
Es conveniente aclarar que errorHandler no captura las advertencias, solo los errores.
Un ejemplo de implementación podría ser:
Vue.config.errorHandler = function(err, vm, info) {
console.log(`Error: ${err.toString()}\nInfo: ${info}`);
}
Al recibir el mismo error que vemos en la segunda imagen de la sección Con ustedes... los errores, esa función produciría la siguiente salida por la consola del navegador:
Error: ReferenceError: x is not defined
Info: render
La función warnHandler
Una función análoga a la anterior, pero para capturar advertencias. Al usar esta función, debemos recordar que no captura advertencias si la aplicación está en modo producción. Por otra parte, la firma de la función difiere un poco de la anterior:
Vue.config.warnHandler = function(msg, vm, trace) {
}
En este caso, los parámetros son:
- msg: el mensaje de la advertencia (String),
- vm: nuestra aplicación de Vue,
- trace: el árbol de componentes, por ejemplo:
La función renderError
Esta función nos permite renderizar los errores en nuestro HTML a través de una función que especifique cómo hacerlo. Un ejemplo podría ser:
const app = new Vue({
el:'#app',
renderError (h, err) {
return h('span', { style: { color: 'blue' }}, err.stack)
}
});
En este caso, a la función de renderizado le indicamos que muestre el error dentro de un span con el texto del stack trace de color azul. De esta manera, si ocurre un error, en lugar de ver el resultado de la plantilla del componente, vemos una página en blanco con un span que contiene el stack trace del error, de color azul.
El hook errorCaptured
Su función muy simple: Cada vez que ocurre un error en uno de los componentes descendientes del componente donde implementamos el hook, se dispara el código implementado. Veamos un ejemplo:
Vue.component('componente-con-error', {
template: `<div>Componente con error</div>`,
created: function() { throw new Error("D'oh!") }
});
new Vue({
template: `<div>
<h1>Hook errorCaptured</h1>
<componente-con-error></componente-con-error>
</div>`,
errorCaptured: function(error, componente, detalles) {
alert(error);
}
});
En este caso, el componente hijo "componente-con-error" lanza un error al crearse. El componente padre captura ese error en su hook errorCaptured y produce un alert con los datos del error.
Pero no todo es color de rosas. Resulta que, al menos en la versión más reciente de Vue (2.6.0+), este hook no puede capturar todos los errores que pueden producirse en el componente. De hecho, solo sirve para errores lanzados desde:
- Funciones de renderizado
- Watchers
- Hooks del ciclo de vida de los componentes
- Manejo de eventos (ej: v-on), pero solo si el código es inline (no funciona si la directiva hace referencia a un método y ese método produce un error).
La función window.onerror
La única opción de la lista que no tiene nada que ver con Vue es la más poderosa y completa: la función window.onerror. Esta función es un gestor de errores global que puede gestionar cualquier error arrojado por JavaScript. Veamos un ejemplo de implementación:
window.onerror = function(msg, origen, linea, columna, error) {
console.log("Error: " + error + "\nMensaje: " + msg + "\n Archivo " + origen + ", línea " + linea + ", columna " + columna);
}
El único punto débil de esta función es que su funcionamiento adecuado con Vue depende de ciertos detalles. En primer lugar, si no definimos una función errorHandler en Vue, entonces los errores parecen no propagarse fuera de los componentes de Vue y, por ende, nunca llegan a la función window.onerror. Por otra parte, si nuestra función errorHandler de Vue contiene a su vez algún error, este no es capturado por window.onerror.
Error boundary
Y acá por fin llegamos al patrón de diseño de hoy. Mediante el hook errorCaptured de Vue, podemos implementar un patrón conocido como error boundary o límite de errores. Podemos pensar en este patrón como algo similar al try-catch de Java. Básicamente, lo primero que debemos hacer es crear un componente que sirva de "límite" para nuestros errores. Veamos un ejemplo:
export default {
name: 'ErrorBoundary',
data: () => ({
error: false
}),
errorCaptured (err, vm, info) {
this.error = true
},
render (h) {
return this.error ? h('p', '¡Ups! Algo salió mal.') : this.$slots.default[0]
}
}
En este caso, creamos un componente llamado ErrorBoundary que solo contiene el hook errorCaptured y una función de renderizado que, en caso de detectar un error, deja de renderizar el componente hijo y, en su lugar, presenta un mensaje de error.
Ahora, solo resta "envolver" el componente problemático con nuestro nuevo componente ErrorBoundary:
<error-boundary>
<componente-que-puede-fallar></componente-que-puede-fallar>
</error-boundary>
De esta manera, cualquier error (soportado) que arroje el componente "envuelto" será gestionado por el componente de manejo de errores que acabamos de hacer.
Ventajas
- Mantiene la lógica de manejo de errores fuera de los componentes.
- Granularidad variable: podemos envolver un componente o varios.
- Nos permite integrar los errores al look&feel de nuestra aplicación.
Para ver un ejemplo de este último punto, podemos echarle un vistazo a este espectacular CodePen de Dillon Chanis.
Bueno, ya llegamos al final de este capítulo de esta serie. Esperamos que los puntos vistos en este artículo los ayuden a comenzar a manejar los errores en sus aplicaciones de Vue. ¡Hasta la próxima!
Fuentes:
- "Handling Errors in Vue Components with Error Boundaries", de Dillon Chanis.
- "Handling Errors in Vue.js", de Raymond Camden.
- "Rental cars, Destiny's Child and error handling with Vue.js", del sitio oficial de Catch.js.
- Documentación de la API de Vue.js.