Independientemente de su nivel de habilidad, los errores o las excepciones son parte de la vida del desarrollador de su aplicación. La inconsistencia del desarrollo web deja muchos lugares donde los errores pueden y lo hacen. La clave de la solución es lidiar con cualquier error imposible (o previsible) para controlar la experiencia del usuario. Con JavaScript, hay una variedad de características técnicas y de idioma que se pueden usar para resolver correctamente cualquier problema.
Es peligroso manejar errores en JavaScript. Si crees en la ley de Murphy, ¡lo que saldrá mal eventualmente saldrá mal! En este artículo, profundizaré en el manejo de errores en JavaScript. Involucraré algunas trampas y buenas prácticas. Finalmente, discutiremos el procesamiento de código asíncrono y el AJAX.
Creo que el modelo de JavaScript, basado en eventos, agrega un rico significado a este idioma. Creo que no hay diferencia entre el motor basado en eventos y el mecanismo de reportaje de errores de este tipo de navegador. Cada vez que ocurre un error, es equivalente a lanzar un evento en un momento determinado. En teoría, podemos manejar errores de lanzamiento de eventos en JavaScript como manejamos los eventos ordinarios. Si esto le suena extraño, concéntrese en comenzar el viaje a continuación. Este artículo solo se dirige al JavaScript del cliente.
Ejemplo
Los ejemplos de código utilizados en este artículo se pueden obtener en GitHub. La página actual se ve así:
Haga clic en cada botón arrojará un error. Simula la excepción del tipo TypeError. La siguiente es la definición y la prueba unitaria de dicho módulo.
Funcion Error () {var foo = {}; return foo.bar ();}Primero, esta función define un objeto vacío Foo. Tenga en cuenta que el método Bar () no está definido en ninguna parte. Usamos pruebas unitarias para verificar que esto arroje un error.
it ('lanza un typeError', function () {debería.throws (target, typeError);});Esta prueba unitaria utiliza las afirmaciones de prueba de las bibliotecas Mocha y debería. JS. Mocha es un marco de prueba en ejecución, y debería.js es una biblioteca de afirmación. Si no está muy familiarizado con ellos, puede explorar sus documentos en línea de forma gratuita. Un caso de prueba generalmente comienza con él ('descripción') y termina con el paso o la falla de la afirmación debería. La ventaja de usar este marco es que se puede probar unidades en el nodo, en lugar de en el navegador. Le sugiero que tome en serio estas pruebas porque validan muchos conceptos básicos clave en JavaScript.
Como se muestra arriba, Error () define un objeto vacío y luego intenta llamar al método en él. Debido a que el método Bar () no existe en este objeto, lanzará una excepción. Confía en mí, en idiomas dinámicos como JavaScript, cualquiera puede cometer tales errores.
Mala demostración
Echemos un vistazo a los malos métodos de manejo de errores. Voy a abstraer la acción incorrecta y la ataré al botón. Así es como se ve la prueba unitaria del controlador:
function badHandler (fn) {try {return fn (); } catch (e) {} return null;}Esta función de procesamiento recibe una función de devolución de llamada FN como dependencia. Entonces la función se llama dentro del controlador. Esta prueba unitaria ejemplos de cómo usar este método.
it ('Devuelve un valor sin errores', function () {var fn = function () {return 1;}; var result = target (fn); resultado. deber.equal (1);}); it ('devuelve un NULL con errores', function () {var fn = function () {tirar error ('error aleatorio');}; var inder = target (fn); deber (resultado) .equal);Como puede ver, si se produce un error, esta extraña forma de manejar devuelve un nulo. Esta función de devolución de llamada fn () apuntará a un método o error legal. El evento de procesamiento de clics a continuación completa el resto.
(function (Handler, Bomb) {var badButton = document.getElementById ('bad'); if (badButton) {badButton.adDeventListener ('click', function () {handler (bomba); console.log ('imaginar, ser promotado para ocultar las mumas');});} (badhandler, error)))));Lo malo es que lo que acabo de conseguir fue un nulo. Esto me confundió mucho cuando estaba pensando en determinar qué estaba mal. Esta estrategia de silencio cuando ocurren errores cubre cada enlace desde el diseño de la experiencia del usuario hasta la corrupción de datos. El lado frustrante seguido fue que tuve que pasar horas de depuración, pero no pude ver el error en el bloque de código Try-Catch. Este extraño procesamiento oculta todos los errores en el código, y supone que todo es normal. Esto se puede ejecutar sin problemas en algunos equipos que no prestan atención a la calidad del código. Sin embargo, estos errores ocultos eventualmente lo obligarán a pasar horas depurando su código. En una solución de múltiples capas que se basa en la pila de llamadas, es posible determinar de dónde proviene el error. Puede ser apropiado manejar silenciosamente la captura de prueba en casos raros. Pero si encuentra un error, lo tratará, lo cual no es una buena solución.
Esta estrategia de falla es que la estrategia de silencio le impulsará que se ocupe mejor de los errores en su código. JavaScript proporciona una forma más elegante de lidiar con este tipo de problema.
Solución no legible
Continuemos, echemos un vistazo al enfoque difícil de entender. Saltaré la parte bien acoplada con el DOM. Esta parte no es diferente del enfoque malo que acabamos de ver. La atención se centra en la parte de la prueba unitaria a continuación que maneja las excepciones.
function feakhandler (fn) {try {return fn (); } catch (e) {Throw Error ('un nuevo error'); }} it ('Devuelve un nuevo error con errores', function () {var fn = functer () {throw new typeError ('type error');}; debería.throws (function () {target (fn);}, error);});Hay una buena mejora en comparación con el mal tratamiento en este momento. La excepción se lanza en la pila de llamadas. Lo que me gusta es que los errores se liberan de la pila, lo cual es de gran ayuda para la depuración. Cuando se lanza una excepción, el intérprete analizará la siguiente función de procesamiento en un nivel en la pila de llamadas. Esto ofrece muchas oportunidades para manejar errores en el nivel superior de la pila de llamadas. Desafortunadamente, debido a que es un error difícil, no puedo ver la información de error original. Así que tengo que mirar a lo largo de la pila de llamadas y encontrar la excepción más primitiva. Pero al menos sé que hay un error en el que se lanza la excepción.
Aunque este manejo de errores ilegible es inocuo, hace que el código sea difícil de entender. Veamos cómo el navegador maneja los errores.
Pila de llamadas
Luego, una forma de lanzar una excepción es agregar un BLOCK DE CÓDIGO PRIT ... Atrapa en el nivel superior de la pila de llamadas. Por ejemplo:
función main (bomba) {try {bomb (); } catch (e) {// manejar todas las cosas de error}}Pero, ¿recuerdas que dije que los navegadores están impulsados por los eventos? Sí, una excepción en JavaScript no es más que un evento. El intérprete detiene el programa en el contexto donde la excepción está ocurriendo actualmente y lanza una excepción. Para confirmar esto, la siguiente es una función global de manejo de eventos OnError que podemos ver. Se parece a esto:
Window.AdDeventListener ('Error', function (e) {var error = e.error; console.log (error);});Este controlador de eventos captura errores en el entorno de ejecución. Los eventos de error producirán varios errores en varios lugares. El punto de este enfoque es manejar los errores centralmente en el código. Al igual que otros eventos, puede usar un controlador global para manejar varios errores. Esto hace que el error maneje solo un solo objetivo si se adhiere a los principios sólidos (responsabilidad única, cerrada, sustitución de Liskov, segregación de interfaz e inversión de dependencia). Puede registrar funciones de manejo de errores en cualquier momento. El intérprete ejecuta estos bucles de funciones. El código se libera de las declaraciones llenas de try ... captura y se vuelve fácil de depurar. La clave de este enfoque es manejar errores que ocurran al igual que los eventos de JavaScript normales.
Ahora, hay una manera de mostrar la pila de llamadas con una función de procesamiento global. ¿Qué podemos hacer con él? Después de todo, tenemos que usar la pila de llamadas.
Grabe la pila de llamadas
La pila de llamadas es muy útil para manejar correcciones de errores. La buena noticia es que el navegador proporciona esta información. A pesar de que el atributo de pila del objeto de error no es estándar en la actualidad, este atributo generalmente es compatible con los navegadores más nuevos.
Entonces, lo genial que podemos hacer es imprimirlo en el servidor:
Window.adDeventListener ('Error', function (e) {var stack = e.error.stack; var message = e.error.ToString (); if (stack) {message + = '/n' + stack;} var xhr = new xmlhttprequest (); xhr.open ('post', '/log', true); xhr.send (});});Puede que no sea obvio en el ejemplo del código, pero este controlador de eventos se activará con el código de error anterior. Como se mencionó anteriormente, cada manejador tiene un solo propósito, lo que deja el código (no se repita sin hacer repetidamente la rueda). Lo que me interesa es cómo capturar estos mensajes en el servidor.
Aquí hay una captura de pantalla del tiempo de ejecución del nodo:
La pila de llamadas es muy útil para depurar el código. Nunca subestimes el papel de la pila de llamadas.
Procesamiento asincrónico
¡Oh, es bastante peligroso manejar el código asíncrono! JavaScript saca el código asincrónico del entorno de ejecución actual. Esto significa que hay un problema con la declaración de intento ... Catch a continuación.
function asynchandler (fn) {try {setTimeOut (function () {fn ();}, 1); } catch (e) {}}Todavía hay una parte restante de esta prueba unitaria:
it ('no captura excepciones con errores', function () {var fn = functer () {throw new typeError ('type error');}; fallidoPromise (function () {target (fn);}). Debs.be.RejedEdWith (typeErRor);}); function fallado });}Tengo que terminar con este controlador con la promesa de verificar la excepción. Tenga en cuenta que aunque mi código está todo en Try ... Catch, todavía aparece una excepción no controlada. Sí, intente ... atrapar solo funciona en un entorno de ejecución separado. Cuando se lanza la excepción, el entorno de ejecución del intérprete ya no es el bloque de prueba de prueba actual. Este comportamiento ocurre de manera similar a las llamadas AJAX. Entonces, ahora hay dos opciones. Una alternativa es detectar excepciones en una devolución de llamada asincrónica:
setTimeOut (function () {try {fn ();} catch (e) {// manejar este error de async}}, 1);Aunque este método es útil, todavía hay mucho margen de mejora. Primero, intente ... Aparte los bloques de código aparecen en todas partes en el código. De hecho, en las llamadas de programación de la década de 1970, querían que su código se retirara. Además, el motor V8 desalienta el uso de los bloques de código TRY ... Catch en funciones (V8 es el motor JavaScript utilizado por Chrome Browser and Node). Recomiendan escribir estos bloques de código que capturan excepciones en la parte superior de la pila de llamadas.
Entonces, ¿qué nos dice esto? Como dije anteriormente, los manejadores de errores globales en cualquier contexto de ejecución son necesarios. Si agrega un manejador de errores a un objeto de ventana, es decir, ¡ya está! ¿No es bueno seguir los principios de seco y sólido? Un controlador de errores global mantendrá su código legible y limpio.
El siguiente es el informe impreso en el manejo de excepciones del lado del servidor. Tenga en cuenta que si usa el código en el ejemplo, la salida puede variar ligeramente dependiendo del navegador que esté utilizando.
Este controlador incluso puede decirme qué error proviene del código asíncrono. Me dice que el error proviene del controlador SetTimeOut (). ¡Qué genial!
Los errores son parte de cada aplicación, pero el manejo adecuado de errores no lo es. Hay al menos dos formas de lidiar con los errores. Una es una solución de falla, silencio, es decir, ignorar los errores en el código. Otra forma es detectar y resolver rápidamente los errores, es decir, detenerse en el error y reproducirse. Creo que he expresado claramente lo que me gusta y por qué me gusta. Mi elección: no ocultes el problema. Nadie te culpará por eventos inesperados en tu programa. Esto es aceptable, para romper los puntos, reproducirse y probar al usuario. En un mundo imperfecto, es importante tener una oportunidad. Los errores son inevitables, y lo que hace es importante para resolver el error. El uso razonable de las características de manejo de errores de JavaScript y la decodificación automática y flexible pueden facilitar la experiencia del usuario, y también facilitar el diagnóstico del desarrollador.