¡Esta es una revisión vieja del documento!
Tabla de Contenidos
Gestión estructurada de excepciones y depuración de código
Módulo perteneciente al curso Programación avanzada con JavaScript y ECMAScript.
Introducción
Asumámoslo: errar es humano, y si eres programador tienes más probabilidades todavía de cometer errores, puesto que escribir código no es nada fácil generalmente.
Cuando uno comienza en programación sorprende descubrir que se dedica más tiempo a corregir errores y excepciones que a crear nuevo código. Es un hecho universal y conviene asumirlo.
Así que, dando por hecho que vas a cometer errores, el propósito de este módulo es repasar las herramientas básicas de depuración con las que cuenta un programador de JavaScript directamente en los navegadores más populares.
En este módulo aprenderás:
- Cómo gestionar excepciones y errores en JavaScript
- Las herramientas para desarrolladores de Chrome y cómo nos ayudan a depurar
- El manejo de la consola y la pestaña “Sources” para depurar nuestro código JavaScript.
Carga asíncrona y diferida de scripts
Aunque una cuestión como “¿dónde coloco una etiqueta en una página?” puede parecer carente de sentido a simple vista, no lo es si pensamos en la enorme cantidad de scripts que se cargan hoy en día en algunas aplicaciones, y en especial en las aplicaciones de tipo Single Page Application (SPA).
Por ejemplo, observa los scripts que carga inicialmente una SPA como es GMail y que además está súper-optimizada por parte de Google:
Como vemos son en total 70 scripts que pesan más de 1,5 MB entre todos, y tardan un tiempo considerable en cargar. Y eso sin contar los scripts que van embebidos en la propia página y que por lo tanto no se cargan de forma externa.
Evidentemente GMail no carga todos estos scripts con etiquetas de tipo <script>, sino que usa otras técnicas y los va cargando bajo demanda a medida que los necesita, etc… Es solo una forma de mostrar la complejidad de scripts de algunas aplicaciones en la actualidad.
Así pues, ante tanto script y con semejante tamaño: ¿En qué parte de una página es mejor colocar los scripts? ¿Es posible ganar rendimiento en su carga de alguna manera?
El procesamiento de los elementos de una página
Si analizamos cómo carga una página sus contenidos podemos ver enseguida que el lugar en el que se coloquen puede tener mucha influencia sobre su rendimiento.
Una vez que el navegador se descarga el código fuente de una página web comienza a analizar sus etiquetas por orden, de arriba a abajo. Es decir, en una página normal primero leerá el doctype, luego la etiqueta raíz HTML, dentro de ésta la cabecera, los elementos de ésta, luego el cuerpo, etc…
En el caso concreto de los scripts, éstos deben ejecutarse a medida que se encuentran en la página. Por ello si colocamos una etiqueta <script> apuntando a un archivo .js externo, antes de continuar cargando el resto de la página y seguir montando el DOM, el navegador debe descargar ese archivo e interpretar y ejecutar su código.
Por este motivo, los scripts que están en la cabecera deben descargarse del servidor y deben ser interpretados antes de hacer nada más. Es evidente que esto, con scripts grandes y complejos, ralentiza la carga de la página.
Sin embargo, el lugar tradicional para ubicar los scripts siempre ha sido la cabecera. Es más, incluso se consideraba una buena práctica porque así estaban todos en el mismo sitio y era todo más ordenado. Claro que eran otros tiempos, y los scripts de la época eran por regla general sencillos y poco pesados.
Que quede claro que aunque el funcionamiento es como el que describo, en una página normal y corriente el efecto de poner los scripts en un lugar o en otro es totalmente indetectable y no se notará disminución de velocidad ni merma del rendimiento. Yo mismo, de hecho, suelo ponerlos siempre en la cabecera por costumbre. Es solamente en páginas con scripts grandes y complejos donde este efecto puede llegar a notarse.
Por todo esto, la recomendación en aplicaciones complejas es que pongamos las etiquetas de script lo más tarde posible en la página, justo antes de la etiqueta de cierre del cuerpo (</body>), de modo que todo el HTML haya ya sido procesado, el DOM esté creado y el retardo debido a cargar e interpretar los scripts no influya en el tiempo de carga percibido por el usuario.
Otra cuestión importante a tener en cuenta es que, en la mayor parte de las aplicaciones SPA grandes, los scripts se crean en forma de módulos, bien de ECMAScript o bien que usen alguna otra técnica de módulos como la API Asynchronous Module Definition o AMD, y se cargan dinámicamente. Esto presenta, entre otras muchas, la ventaja de que solo se cargan los módulos cuando se necesitan y no antes (acelerando la carga global de la página) y que además definimos dependencias entre ellos, el orden adecuado para cargar, la carga asíncrona de los mismos, etc…
El atributo async
Otra opción muy interesante que nos habilita HTML5 es especificar explícitamente que la carga de nuestros scripts debe hacerse de manera asíncrona.
En realidad, si los scripts que debemos cargar no ejecutan código, sino que se encargan simplemente de definir clases y módulos que luego vamos a utilizar al terminar de cargar la página, obligar a que éstos se procesen e interpreten durante la carga no es necesario.
Para conseguirlo lo único que tenemos que hacer es marcar la etiqueta script con el atributo async, así:
<script type="text/javascript" src="scripts/miScript.js" async></script>
De este modo cuando el intérprete del navegador se encuentra esta etiqueta en una página lo que hace es empezar la descarga e interpretación del script en segundo plano, mientras sigue con el procesamiento del resto del código fuente de la página. Así, no se detiene la carga de la página como pasaría normalmente.
El soporte de esta etiqueta es casi universal como podemos ver en CanIUse:
Y solo los navegadores antiguos (concretamente IE9 o anterior) cargarían los scripts del modo normal.
Dos cosas importantes a tener en cuenta con este atributo:
- Solo funciona en scripts externos, es decir, scripts que tengan el atributo src para apuntar a un archivo .js. En scripts en línea, por supuesto, carece de sentido.
- Dado que los scripts se cargan en segundo plano puede darse la circunstancia de que archivos que están más adelante en el código pero son más pequeños se carguen e interpreten antes que archivos .js anteriores en el código. Si el orden de ejecución es importante no nos servirá, así que mucho ojo.
El atributo defer
Otro atributo muy interesante para los scripts es defer. Este atributo lo que consigue es diferir la carga e interpretación de los scripts marcados con el mismo hasta que la página se haya cargado por completo, independientemente de donde estén colocados en el código.
Su sintaxis es muy sencilla:
<script type="text/javascript" src="scripts/miScript.js" defer></script>
De este modo la etiqueta no se utiliza hasta que la página ha cargado por completo. Así, el incluir los scripts aunque sea en la cabecera no ralentiza en absoluto la carga de la página.
El soporte es universal también:
En este caso el orden de ejecución sí que se conserva por lo que no tiene el problema que causaba el uso de async, y garantizamos un orden de ejecución.
Del mismo modo este atributo solo tiene efecto en scripts externos, y no en scripts en línea.
Internet Explorer fue el “inventor” de este atributo allá por el año 1997 ¡con IE4!. En versiones antiguas de este navegador (hasta la 9, por eso están en un verde diferente en la figura anterior) el atributo defer sí que actuaba sobre los fragmentos en línea, lo cual cambia el comportamiento que tenemos actualmente. A mí me parecía más lógico, la verdad. Aunque no influye demasiado en el código, salvo por el orden de ejecución, puede dar algunos problemas en versiones antiguas de IE si no lo tenemos en cuenta.
En resumen
En páginas y aplicaciones normales no tiene demasiada importancia dónde coloquemos las etiquetas de script dentro del código HTML. En aplicaciones grandes y en las que cada milésima de segundo cuenta para el rendimiento tenemos diversas opciones para cargar scripts de manera que no impacten en la velocidad de carga de la página:
- Colocarlos al final de todo del cuerpo, antes de la etiqueta
</body>. - Usar alguna API de carga asíncrona de módulos, como AMD, módulos de ECMAScript o bibliotecas especializadas como WebPack.
- Utilizar el atributo
async. - Usar el atributo
defer.
Gestión estructurada de excepciones en JavaScript
Hay una verdad universal en el desarrollo de software y es que “las excepciones son inevitables”.
Por muy bien que esté nuestro código siempre se van a dar situaciones en las que fallará el programa debido a cualquier tipo de cuestión: valores para una función con los que no contábamos, algo extraño que introduce un usuario, cambios en los navegadores, fallos en las comunicaciones…
Pero que sean inevitables, no quiere decir que no podamos preverlos y tratar de gestionarlos para limitar sus consecuencias. Eso es lo que nos permite conseguir la gestión de excepciones.
En JavaScript tenemos la posibilidad de utilizar la conocida estructura try-catch-finally, propia de muchos otros lenguajes, sólo que con algunas limitaciones.
Los bloques de este tipo nos permiten definir una zona de código que queremos proteger (try), otra zona que es lo que queremos hacer en caso de que algo falle (catch) y lo que queremos que se ejecute siempre, pase lo que pase (finally).
La sintaxis general es la siguiente:
try { //Código a proteger } catch(e) { /Código a ejecutar si hay un fallo } finally { /Código a ejecutar siempre, haya o no fallos }
Por ejemplo, un caso muy típico es que intentemos hacer uso de una característica que no está presente en todos los navegadores y que por lo tanto puede fallar en algunos casos. Lo que debemos hacer es gestionar esos posibles fallos y actuar en consecuencia para utilizar una estrategia diferente o simplemente informar de este hecho al usuario.
Podríamos escribir un código como este:
try { hacerAlgo(); } catch (e) { alert("Se ha producido un error de tipo '" + e.name + "': " + e.message); }
Como vemos, la cláusula finally no es obligatoria y podemos obviarla.
Dentro del bloque catch podemos obtener una referencia a un objeto con información sobre el error y mostrar, por ejemplo, los detalles del mensaje.
La propiedad message del error nos devuelve información sobre por qué se ha producido.
La propiedad name nos indica el tipo de error, que puede ser de alguno de los siguientes:
- Error: error genérico lanzado por nosotros, como veremos en un instante.
- RangeError: cuando se produce un error de fuera de rango de valores válidos, por ejemplo, al pasar una cadena a una función que esperaba un número.
- ReferenceError: al hacer referencia a algo que no existe, por ejemplo llamando a una función que no está definida.
- TypeError: excepciones relacionadas con el uso del tipo incorrecto en una variable.
- EvalError: errores producidos al evaluar JavaScript dinámicamente con la función
eval. - SyntaxError: cuando al intentar ejecutar dinámicamente JavaScript con
evalse producen errores de sintaxis.
Generar excepciones propias
Podemos lanzar cualquiera de estos errores por nosotros mismos mediante la instrucción throw:
throw new Error("Se ha producido un error de comunicaciones");
Al lanzar una excepción por código detenemos la ejecución en ese punto, por lo que si está dentro de una función se saldría fuera de ésta.
Este sería el modo más habitual, instanciando un objeto de tipo Error genérico, pero podemos lanzar errores específicos de los indicados en la lista para denotar situaciones concretas.
Por ejemplo, imaginemos una función muy sencilla para calcular porcentajes de una cantidad sobre otra (sería una simple división). Si se le pasa un cero como segunda cantidad tendríamos una división entre cero, por lo que sería un valor no válido para esta función. Por otro lado, sólo podemos trabajar con números, así que si se nos pasa una cadena de texto u otro tipo deberíamos indicar que son valores no válidos también. Podríamos escribir una función como esta:
//Calcula el porcentaje de a sobre b function porcentaje(a, b) { if (typeof(a) != "number" || typeof(b) != "number") throw new TypeError("Sólo se pueden usar números"); if ( b==0 ) throw new RangeError("El segundo número no puede ser cero."); return (a/b)*100; } try { alert( porcentaje("2", 0) ); } catch (e) { alert("Se ha producido un error de tipo '" + e.name + "': " + e.message); }
Si ejecutamos este fragmento obtendremos una excepción (capturada por el catch) que nos indicará que sólo podemos utilizar números como argumentos de la misma (primera comprobación). Si cambiamos el “2” por un verdadero número (no una cadena como en el fragmento), lo que saltará entonces es una excepción de tipo RangeError para indicar que el valor 0 no es válido como segundo argumento.
De esta forma nos estamos anticipando a posibles cosas que pueden ocurrir, y generamos errores capaces de ser detectados en otras partes del código.
También podemos lanzar errores de nuestro propio tipo, que no sean ninguno de los de la lista, de la siguiente manera:
throw {name: "MiTipoDeError", message: "El mensaje de error"};
Los errores se transmiten por la pila de llamadas del programa, de modo que los podemos capturar en cualquier nivel de ésta. A este fenómeno se le llama convergencia de excepciones o “excepción bubbling”.
Así, si tenemos una función que llama a otra y ésta a una tercera, y se produce un error en la última, aunque el try-catch esté ubicado en la primera de todas, se capturará perfectamente. Si lo capturamos no sigue convergiendo. Es decir, si lo capturamos en la segunda función de la pila de llamadas, el error no se podrá detectar en un try-catch de la primera (o de la ventana, el global), a menos que lo relancemos con una instrucción throw.
Al contrario que en otros lenguajes de programación no es posible definir varios bloques catch especializados, uno por cada tipo de excepción. Si queremos tomar una acción distinta según sea el tipo de error producido, lo que tenemos que hacer es verificar el nombre de la excepción y actuar en consecuencia:
try { alert( porcentaje("2", 0) ); //Primer parámetro no válido } catch (e) { switch (e.name) { case "TypeError": alert( "Se ha pasado un valor no numérico"); break; case "RangeError": alert("No se puede dividir por cero"); break; default: alert("Se ha producido un error de tipo '" + e.name + "': " + e.message); } }
En el catch distinguimos el tipo de error a partir de su nombre actuando en consecuencia.
En un caso real, por ejemplo, si detectamos que alguno de los parámetros es del tipo incorrecto podemos rellamar a la función transformándolos antes en números con parseInt. Cada caso requerirá una acción correctora distinta, si es que es posible corregir de algún modo.
Anidamiento
Los bloques try-catch se pueden anidar.
Por ejemplo, definamos una función que nos permite obtener una referencia al objeto XMLHttpRequest (XHR) que permite lanzar peticiones HTTP en segundo plano para AJAX. Podría ser la siguiente:
function getXHR() { var xhr=null; if (window.ActiveXObject){ //IE try { xhr=new ActiveXObject("Msxml2.XMLHTTP"); } catch (e){ try{ xhr=new ActiveXObject("Microsoft.XMLHTTP"); } catch (e){ alert("Tu navegador no soporta AJAX!!"); } } } else { if (window.XMLHttpRequest) //Resto de navegadores xhr=new XMLHttpRequest() else alert("Tu navegador no soporta AJAX!!"); } }
Dentro del bloque correspondiente a Internet Explorer (el primero) intentamos instanciar el objeto usando una de las dos sintaxis posibles (según la versión del navegador). Si el primer intento falla en un navegador muy antiguo, capturamos la excepción y lo intentamos de nuevo. En el bloque catch hacemos el nuevo intento también empleando un try-catch por si fallara (sería un navegador realmente antiguo), en cuyo caso desistimos de instanciar el objeto.
Los bloques try-catch-finally añaden bastante carga al motor de JavaScript. Si abusamos de ellos podríamos notarlo en el rendimiento, si bien esto sólo es importante en caso de scripts complejos y grandes donde el rendimiento sea clave.
Antiguamente se usaban códigos devueltos como valores de retorno de las funciones para indicar que se había producido algún tipo de excepción o error y se había gestionado. Este tipo de técnicas está obsoleto y no deberíamos utilizarlo. La gestión estructurada de excepciones, que es lo que se habilita gracias al uso de try-catch-finally y la convergencia de eventos, es la manera actual y recomendada de gestionar cualquier tipo de excepción que se produzca en nuestro código.
Captura de errores a nivel de página
Un último detalle relacionado con la captura de errores.
En una página web siempre podremos capturar cualquier error que se produzca y no hayamos gestionando, sea donde sea, utilizando el evento global error de la ventana, que se puede asignar mediante una propiedad global onerror o mediante el método tradicional addEventListener.
En caso de asignarlo con onerror, se espera una función con cinco parámetros opcionales:
- El mensaje de error
- La URL de la página en la que se ha producido
- El número de línea en el que se ha producido
- El número de columna en el que se ha producido
- Una referencia a un objeto de tipo Error que, realmente, lo único que aporta es poder ver su tipo exacto llegado el caso.
Por ejemplo:
window.onerror = LogJSError; function LogJSError(msg, url, linea) { console.log("Se ha producido un error: " + msg + " en la línea " + linea ); }
Puedes capturarlo usando el modelo de eventos del DOM si quieres crear una biblioteca o usarlo de manera más profesional. En este caso, el objeto evento que se le pasa al manejador de eventos es de tipo ErrorEvent y proporciona las propiedades:
message: el mensaje de errorlineno: el número de línea en el que se ha producido el errorcolno: la columna dentro de esa líneafilename: el nombre (y ruta completa) del archivo en el que se ha poducido el error.
Funcionaría así:
window.addEventListener('error', function (ev){ console.log("Se ha producido un error: " + ev.message + " en la línea " + ev.lineno + " del archivo " + ev.filename); })
Este manejador global nos puede resultar útil en ocasiones.
El modo estricto de JavaScript
Modo restringido de trabajo que podemos activar de manera voluntaria y nos permite tener menos errores. Permite también al intérprete de JavaScript ser más eficiente.
var miVar = 1; function test() { mivar = 5; alert(miVar); } test();
Si ejecutásemos el código anterior, no habrá errores. El error tipográfico que cometimos, no escribiendo correctamente la variable (JavaScript distingue mayúsculas de minúsculas), JavaScript lo interpretó como que teníamos dos variables (miVar y mivar).
Si establecemos el modo estricto:
"use strict"; var miVar = 1; function test() { mivar = 5; alert(miVar); } test();
El código fallará con el siguiente mensaje:
Uncaught ReferenceError: mivar is not defined
Podríamos activar el modo estricto en funciones y no en todo el script:
var miVar = 1; function test() { "use strict"; mivar = 5; alert(miVar); } test();
El modo estricto también es útil para prevenir la redefinición de parámetros o propiedades.
"use strict"; // Nos equivocamos a propósito en el nombre de // uno de los parámetros, que repetimos function test(p1, p2, p1) { alert(p1); alert(p2); } test(1, 2, 3);
Al tratar de ejecutarlo, nos mostrará el siguiente error:
Uncaught SyntaxError: Strict mode function may not have duplicate parameter names
Otro ejemplo:
"use strict"; var objeto = { prop1: "Hola", prop2: "Adiós", prop1: "Otra cosa" }; alert(objeto.prop1);
Si no hubiésemos usado el modo estricto, JavaScript se quedará con la última propiedad definida (Otra cosa);
También podemos usar el modo estricto para restringir el ámbito de las variables:
// Evaluación de código JavaScript eval("var x = 5;"); alert(x);
En el modo estricto, las variables definidas en la función eval, no tienen ámbito global, así que nos mostraría el siguiente error:
Uncaught ReferenceError: x is not defined
Con el modo estricto tampoco prodíamos redefinir funciones existentes:
"use strict"; // Lo siguiente no estaría permitido: eval = "Hola"; arguments = "Adiós";
Mostraría el error:
Uncaught SyntaxError: Unexpected eval or arguments in strict mode
function test(p1) { p1 = "10"; alert(p1); alert(arguments[0]) } test(5);
Los alerts devuelven el mismo valor, pero en modo estricto tendríamos el valor que se le pasa a la función.
- Strict mode (en la MDN)
Herramientas del desarrollador
Todos los navegadores Web incorporan de serie herramientas avanzadas para los desarrolladores. No es necesario instalar nada, ni tampoco hay que gastar dinero alguno: todo lo que necesitas para depurar un script lo tienes incorporado en todos los navegadores modernos.
Hasta Internet Explorer, por ejemplo, incorpora de serie (¡desde su versión 8! desde hace ya muchísimos años) unas herramientas llamadas “F12 Developer Tools” con casi todo lo que uno pueda necesitar para depurar JavaScript, HTML y CSS. Para lanzarlas, lo único que tenemos que hacer es visitar la página que queremos depurar y pulsar la tecla F12 (de ahí el nombre). Ya las hemos visto en uso, aunque por encima, a lo largo del curso en diversos vídeos.
Otro atajo habitual para abrirlas, si no te funciona F12, es usar la combinación CTRL+MAYs+I (o CMD+OPT+Ctrl en macOS).
Aunque con pequeñas diferencias entre ellas, todas estas herramientas ofrecen multitud de utilidades para depurar: inspección de elementos, análisis de hojas de estilo, estilos resultantes, modificación al vuelo de la página, depuración paso a paso de scripts, medidor de rendimiento de descarga de páginas, análisis de tráfico de red… y otras muchas.
Google Chrome es casi el estándar de facto en estas herramientas, ya que es con diferencia el navegador más usado del mercado. Además, muchos otros navegadores como Opera, Brave o incluso Microsoft Edge utilizan Chrome por debajo, y usan por lo tanto las mismas herramientas.
Dado que en realidad cualquiera de los navegadores modernos ofrece casi lo mismo, aunque cambie la interfaz, pero en cualquier caso todo lo necesario para trabajar a buen nivel eliminando errores en JavaScript, y no podemos explicar con detalle el uso de todos, vamos a quedarnos con una de ellas.
Las que he elegido son las herramientas de Google Chrome. El motivo principal para decidirme por éstas es que son seguramente las más completas y potentes, Chrome es el navegador con mayor cuota de uso con mucha diferencia, y son idénticas en todos los sistemas operativos. De este modo, uses Windows, Linux o Mac, las herramientas de depuración para desarrolladores son exactamente iguales y funcionan con la misma capacidad.
Lo aprendido en Chrome nos servirá con ligeros cambios en los demás navegadores. Sólo hay que buscar en dónde está el menú o botón correspondiente, ya que en realidad todas las herramientas son muy parecidas.
Las herramientas de desarrollo de Chrome son un mundo y tienen infinidad de pequeñas características y detalles de interés. Explicarlo todo es imposible (necesitaríamos un curso propio solo de esto) y además le están incorporando pequeñas mejoras constantemente. En esta sección vamos a ver lo más importante. Para el resto te refiero a la documentación oficial de las herramientas del desarrollador de Chrome. Las de Firefox, por si te interesa indagar también por tu cuenta (deberías) tienen su documentación oficial aquí.
Examinando el DOM
Desde las herramientas de desarrollador, en la pestaña Elementos podemos ver todos los elementos que componen una página (etiquetas HTML).
Al colocarnos encima de cada elemento HTML, se resalta en la página para saber a qué corresponde. También podemos hacerlo al revés, es decir, colocarnos sobre un elemento de la web y se resaltará qué parte del código corresponde. Para esto último, hay que pulsar en el icono con una flecha que hay a la izquierda de la pestaña Elementos.
Podemos editar directamente el HTML y el CSS para ver cómo cambiaría la web. Este cambio es solo local y temporal. Es útil mientras desarrollamos.
Si marcamos un elemento en el DOM y pulsamos H, lo ocultará. Pulsando de nuevo H volverá a mostrarlo.
Podemos ver todos los cambios que hemos hecho, podemos elegir Cambios y ver qué cambios hemos hecho respecto al contenido original
Emulación de dispositivos y diseño responsivo
En Google Chrome, en las herramientas para desarrolladores, hay un botón en la parte superior izquierda que permite activar / desactivar la emulación de dispositivos. Podremos modificar las dimensiones, el tipo de dispositivo, si es un dispositivo de escritorio o móvil, regular la “potencia” del dispositivo, etc.
Tomando el control de los recursos de una página
En Google Chrome, en la pestaña Aplicación podremos examinar cualquier recurso de una aplicación: scripts, cookies, imágenes, tipografías, lo que se almacene en local, etc.
Análisis de la descarga de una página
En Google Chrome, desde la pestaña Network (Red), que permite analizar las comunicaciones de red que tiene el navegador con el servidor o servidores con los que se comunica.
Podemos ver peticiones de imágenes, documentos, scripts, etc. con sus estados HTTP (200, 204, 404…), versión de protocolo HTTP, dominio, tamaño de los ficheros, tiempo de descarga…
Podemos también ver el detalle de la petición de cada recurso (cabeceras que se envían, las que se reciben, tiempos…).
Depuración con las herramientas del desarrollador
Uso manual de la consola del navegador
En Google Chrome, en las herramientas para desarrolladores, vamos a la pestaña Consola. En esta pestaña podemos ver mensajes que muestra la página, errores y también nos permite ejecutar código en la página web. Cualquier instrucción JavaScript que escribamos, afectaría a la página que estamos visualizando.
EL dólar ($) es un atajo de documento.querySelector, es decir, podemos hacer $('#main-nav li'). Para obtener más de un elemento con el mismo selector, utilizaremos dos símbolos de dólar: $$('.link').
Comandos interesantes de la consola de Google Chrome:
copydir: muestra las propiedades JavaScript que tendría un elemento.monitor: monitoriza el uso de lo que le indiquemos por parámetro.unmonitor: para dejar de monitorizar cierta función (lo contrario de la función anterior).monitorEvents: notifica cuando se producen ciertos eventos.unmonitorEvents: para dejar las notificaciones de la función anterior.
Depuración - puntos de interrupción simples y avanzados
En Google Chrome, en las herramientas para desarrolladores, podemos establecer puntos de interrupción yendo a la pestaña Sources y marcando un número de línea. Quedará establecido un punto de interrupción. Al ejecutar el programa, cuando llegue a esa línea, se detendrá la ejecución.
Cuando esté detenida la ejecución, en la barra lateral podemos obtener información como la pila de llamadas (Call Stack) (se lee de abajo a arriba), el valor de las variables en ese momento… También tenemos unos botones para continuar la ejecución paso a paso (F10).
En la barra lateral también hay otra opción interesante: Watch. Sirve para escribir expresiones y “vigilar” cómo van variando. Esto no ahorra tener que estar escribiendo expresiones en consola repetidamente cuando estamos depurando para ver sus valores.
Si queremos establecer cuándo detender la ejecución, tenemos la posibilidad de añadir un punto de interrupción condicional. Botón derecho sobre el número de línea deseado y Add coinditional breakpoint. Pondríamos una expresión (que debe devolver true o false) que hará que pare la ejecución.
Además de los puntos de interrupción, en Google Chrome tenemos la posibilidad de añadir un logpoint. Con el botón derecho en el número de línea deseado, elegimos Add logpoint…. El código no se parará ahí, pero mostrará por consola lo que le hayamos indicado. Es útil para saber lo que valen las variables, por ejemplo. Puede ser más cómodo que un console.log().
Otros tipos de puntos de interrupción
Además de los puntos e interrupción en posiciones concretas del código que nos interesen, el depurador nos permite incluir interrupciones automáticas en base a eventos que ocurren, y que por lo tanto no están determinadas de antemano. Tenemos varios tipos, todos muy útiles en ciertas ocasiones:
- Cambios en el DOM: si vamos al panel de elementos, seleccionamos uno y pulsamos con el botón derecho, veremos que se nos ofrecen tres opciones de interrupción: Break in subtree modifications, que detendrá el código si se cambia cualquier elemento hijo de este elemento; Break on attributes modifications, para pausar cuando se cambie cualquier atributo del mismo y Break on node removal, que detendrá la ejecución en caso de que el elemento se elimine de la página. Son muy útiles en páginas que manipulan mucho el DOM, ya que a veces es difícil saber el código concreto que provoca los cambios. Aparecerán en el panel DOM Breakpoints del lateral derecho de la pestaña Sources:
- Acciones XHR: XHR es el acrónimo de XML Http Request, que es el objeto que se encarga de lanzar peticiones en segundo plano a los servidores, fundamentalmente para trabajo AJAX, aunque también se refiere en este contexto a peticiones realizadas con el método
fetchde ECMAScript 6 o posterior. Como estas peticiones pueden ser difíciles de depurar, se pueden añadir puntos de interrupción automáticos cuando se produzca alguna petición AJAX al servidor. Se puede hacer en todos los casos o si la URL solicitada incluye una determinada cadena. Para añadirlos vamos al panel XHR breakpoints en el lateral de la pestaña Sources y pulsamos el botón+que hay allí a tal efecto:
- Eventos de elementos: si queremos verificar qué pasa cuando ocurre un determinado evento en la página o en alguno de sus elementos podemos localizar el código concreto que lo va a gestionar y poner puntos de interrupción en el mismo. Sin embargo, esto no es tan fácil muchas veces ya que puede haber varios manejadores de eventos asociados al mismo evento, se establecen automáticamente con una biblioteca externa como jQuery en donde a lo mejor no es fácil localizar ese código para ciertos eventos concretos, etc… Si utilizamos el panel lateral Eventlisteners breakpoints podemos elegir de una extensa lista qué tipos de eventos queremos detectar, deteniéndose la ejecución cuando ocurran en cualquier elemento:
¿Para qué sirven las excepciones?
Las excepciones se producen, como su propio nombre indica, cuando ocurre algo que no sea el flujo normal del programa. Cuando esto ocurre tenemos dos opciones: hacer caso omiso o gestionarlas.
Si no les haces caso las captura el navegador y se interrumpe la ejecución de JavaScript, por lo que si tu página depende de un script para su funcionalidad puede quedarse parada.
Lo normal es lo otro: gestionarlas.
Cuando escribes un código debes tener en cuenta qué cosas pueden ocurrir en éste que produzcan una excepción. Si tienes en cuenta estas posibles excepciones puedes capturarlas cuando ocurran y en vez de simplemente dejar que falle el programa, tomar medidas correctoras.
Vamos a poner un ejemplo para verlo mejor. Imagínate que tu programa debe leer cierta información de una dirección remota (de otro servidor) mediante AJAX, y luego hace algo con lo que obtiene de ella (lo que sea: no es importante en nuestro caso). En condiciones normales el programa podrá leer esos datos y y procesarlos, pero ¿qué pasa si, por lo que sea, un día al intentar leer la dirección el servidor está caído o devuelve un error? ¿O no tienes conexión a Internet cuando intentas leerlo? ¿O el servidor cambia el formato de los datos y tu código no sabe interpretarlos?
Hay muchas situaciones como estas, excepcionales, pero que pueden ocurrir. Si no las tienes en cuenta se produce un eror y tu programa “rompe” y la página se para. Sin embargo, cuando escribes un programa que haga esto del ejemplo debes tener en cuenta todas estas posibilidades y evitar que el programa rompa cuando ocurren. Para eso capturas las excepciones con try-catch, y en lugar de dejar que programa falle (o la página se detenga), las gestionas de otra manera más “amigable”, por ejemplo sacando un mensaje al usuario diciendo que hay problemas en las comunicaciones, o enviando un informe a algún lado… Otras veces incluso podrás tomar medidas correctivas que te permitan seguir trabajando. En nuestro ejemplo podría ser seguir usando los datos que recuperaste la última vez en lugar de simplemente dar un error, pero dependerá de tu programa concreto.
De todos modos esto plantea una pregunta lógica: ¿por qué no ponemos un enorme try-catch envolviendo todo nuestro código (o todas nuestas funciones) para evitar que las aplicaciones rompan?
Bueno, esto es algo que algunos lenguajes antiguos hacían pues carecían de gestión estructurada de errores, y es un grave problema, porque al final no sabes nunca qué está pasando con tu programa, si falla o no ni dónde, etc… Este artículo explica cómo tan malo es capturarlo todo como no capturar nada, y los problemas que pueden surgirte por hacerlo de una forma o de la otra.
Escoger bien en dónde se capturan las excepciones es también una tarea crítica muchas veces. De hecho, una parte muy importante de las excepciones no es tan solo aprender a capturarlas, sino aprender a lanzarlas.
Antiguamente, antes de que hubiera gestión estructurada de excepciones (es decir, éstas no convergían hacia arriba por la pila de llamadas) lo más habitual cuando se quería señalar una excepción al ejecutar una función era devolver un valor especial. Por ejemplo, si la función devolvía numeros positivos, al producirse una excepción o error (por ejemplo, que un parámetro no era un número, o una divisón entre cero) se devolvía un número negativo para señalarlo. A lo mejor cada número negativo tenía un significado. Otras veces se devolvía un texto especial. En la función llamante, justo “encima” en la pila de llamadas, debías gestionar ese error viendo el valor devuelto y actuando en consecuencia (con un switch a lo mejor). De hecho las primeras versiones de JavaScript, en los '90, funcionaban así. Eso es una verdadera pesadilla para gestionarlo en aplicaciones grandes, además de que crea código muy difícil de mantener en el futuro.
De hecho lo normal es que generes excepciones en algunas de tus funciones y que estén documentadas las excepciones que puede generar cada función.
Con la gestión estructurada de excepciones la cosa se simplifica mucho. Puedes devolver tus propias excepciones para señalar casos no convencionales, esa excepción la puedes gestionar donde quieras en la pila de llamadas, el mensaje de error ya va incluido en la propia excepción… En lenguajes más avanzados que JavaScript puedes además meter diferentes catch para gestionar diferentes tipos de excepciones, lo cual te da un control granular muy interesante.







