Cómo Cargar Scripts Eficientemente con Async y Defer

htmlJavascript

Cuando cargas scripts de JavaScript en un documento HTML necesitas tener cuidado con el tiempo de carga de la página, ya que dependiendo del lugar donde cargues los scripts o del método de carga utilizado podrías generar un gran impacto en el rendimiento de la misma.

Lo más habitual es incluir los scripts de este modo:

<script src="script.js"></script>

Cuando el navegador analice la página y llegue a la línea en la que se define el script, éste se cargará mediante una petición adicional al servidor, ejecutándose posteriormente el script. Cuando este proceso finalice, el navegador continuará analizando el resto de los elementos de la página.

Si algún script definido en la cabecera de la página resulta muy pesado y tarda bastante en cargarse o si tarda demasiado en ejecutase, los visitantes verán una página en blanco hasta que el script se cargue por completo y se termine de ejecutar. Esto no es algo que alguien desee, especialmente cuando un visitante accede a la página desde un dispositivo móvil con una conexión de red lenta.

En dónde colocar los scripts

Vamos a ver primero los diferentes lugares en los que puedes colocar los scripts en un documento HTML.

Script en la cabecera

Cuando aprendes HTML resulta todavía muy habitual que te digan que los scripts se incluyen en la cabecera del documento, entre las etiquetas <head> y </head>, tal y como vemos en este ejemplo:

<html>
  <head>
    <title>Título</title>
    <script src="script.js"></script>
  </head>
  <body>
    ...
  </body>
</html>

Sin embargo, cuando el navegador analice la página y se encuentre con el script que hemos definido, lo cargará y lo ejecutará. Una vez haya realizado esta tarea, continuará analizando el body de la página. Sin embargo, esto conlleva una serie de problemas. Por un lado se introduce un retraso innecesario en la carga de la página y por otro, puede que ocurra algún error si el script se ejecuta y hace referencia a algún elemento de la página que todavía no se ha cargado.

A continuación puedes ver una representación del proceso de carga y del rendimiento de un script en el head de una página:

Tal y como puedes ver, el análisis del código HTML se pausa hasta que el script se carga y se ejecuta. Una vez se haya cargado y ejecutado, el análisis continuará. A continuación vamos a ver cómo solucionar estos problemas.

Script en el body

Las solución a los problemas anteriores consiste en incluir el script justo antes de la etiqueta de cierre </body>. De este modo, el script se cargará y se ejecutará una vez la se haya analizado el código HTML, lo que conlleva una gran mejora en el rendimiento de la página:

<html>
  <head>
    <title>Título</title>
  </head>
  <body>
    ...
    <script src="script.js"></script>
  </body>
</html>

Este método es el mejor que puedes usar si es que necesitas dar soporte a navegadores antiguos que todavía no soportan los atributos async y defer. Si quieres, también puedes ver en detalle el siguiente artículo, en el que se explica con más detalle en dónde debes cargar los scripts.

A continuación puedes ver una representación del proceso de carga y del rendimiento de un script en el body de una página, justo antes del cierre de la etiqueta </body>:

Tal y como puedes comprobar, no existen pausas desde que se inicia el análisis y el renderizado del código HTML hasta el que finaliza. Los scripts se cargan y se ejecutan una vez finaliza el renderizado. De este modo podemos mostrar la página al usuario antes de que los scripts se pidan al servidor.

Sin embargo, aunque el método que hemos visto soluciona muchos problemas, existen dos métodos más modernos mediante los cuales podemos cargar los scripts en la cabecera sin los problemas mencionados.

Carga scripts con async y defer

En los navegadores modernos, la etiqueta script acepta los atributos booleanos async y defer. Estos atributos indican el método de carga del script. En caso de especificar ambos atributos, el atributo async tiene precedencia sobre defer, salvo que el navegador soporte el atributo defer pero todavía no soporte async, en cuyo caso se seleccionará defer.

Script el head con async

El atributo async solamente tiene sentido cuando cargamos el script en el head de la página. Para cargar un script mediante este método basta con agregar el atributo en la etiqueta del script:

<html>
  <head>
    <title>Título</title>
    <script async src="script.js"></script>
  </head>
  <body>
    ...
  </body>
</html>

Cuando usas async el script se carga de forma asíncrona al mismo tiempo que se analiza el el código HTML de la página. Sin embargo, cuando el script está listo, el análisis se pausa para ejecutar el script. Una vez termine la ejecución del script, el análisis de código HTML continúa hasta su finalización.

A continuación puedes ver una representación del proceso de carga y del rendimiento de un script que usa el atributo async en el head de una página:

Mediante este método tenemos la ventaja de la carga en paralelo del script, pero sigue habiendo esperas.

Si usas el atributo async cuando cargas el script en el body, será inútil.

Script en el head con defer

Al igual que ocurre con el atributo async, el atributo defer solamente tiene sentido cuando cargamos el script en el head de la página. Para cargar un script mediante este método simplemente debes agregar el atributo en la etiqueta del script:

<html>
  <head>
    <title>Título</title>
    <script defer src="script.js"></script>
  </head>
  <body>
    ...
  </body>
</html>

Cuando usas defer el script se carga de forma asíncrona a la vez que se analiza el código HTML de la página. Sin embargo, el script no se ejecutará hasta que finalice el análisis del código HTML de la página, tal y como ocurría cuando colocábamos el script antes del cierre de la etiqueta body. En esta ocasión tenemos la ventaja de que el script ya se habrá cargado, ganando un valioso tiempo.

A continuación puedes ver una representación del proceso de carga y del rendimiento de un script que usa el atributo defer en el head de una página:

Con este método tenemos la ventaja de colocar el script en el body y la ventaja de que el script se cargue en paralelo a la vez que se analiza el código HTML. En términos de velocidad, este es sin duda el mejor método.

Si usas el atributo defer será inútil cuando cargas el script en el body.

Comparativa entre async y defer

Vamos a ver qué metodo utilizar en base a diferentes parámetros que también utiliza la utilidad PageSpeed Insights de Google.

Bloqueo del análisis HTML

Lo primero que hace el navegador es analizar el código HTML de la página. Cuando usamos async se bloquea el análisis del código de la página, mientras que cuando usamos defer el análisis del código HTML no se pausa hasta que finaliza.

Bloqueo del renderizado HTML

Si bloqueamos el análisis del código HTML, también estamos bloqueando indirectamente el renderizado del mismo, ya que el renderizado se realiza tras el análisis. Si embargo, aún cuando no haya pausas en el análisis del código HTML, podría bloquearse el renderizado del mismo dependiendo de la programación de los archivos JavaScript.

Aunque es un primero paso, ni el uso de async ni el uso de defer garantizan que no se vaya a bloquear el renderizado. Para evitar el bloqueo del renderizado tendrás que asegurarte de que los scripts se ejecuten tras el evento onLoad.

Evento domInteractive

Aunque haya finalizado el renderizado del código HTML, la página todavía no será interactiva para el usuario. El evento domInteractive será el que indique el inicio de dicha interacción, siendo además una de las métricas de rendimiento de Google, por lo que es algo muy importante.

Los scripts cargados mediante defer se ejecutarán justo después de que el navegador emita el evento domInteractive, que ocurrirá una vez el DOM se haya inicializado, para lo cual, el código HTML se tendrá que haber cargado y ejecutado.

Los archivos CSS y las imágenes todavía no se habrán analizado ni cargado llegados a este punto. Una vez estén cargados, el navegador emitirá el evento domComplete, y luego el evento onLoad.

Orden de ejecución

Cuando cargamos scripts usando defer, los scripts se ejecutarán uno tras otro, en el orden que los hemos definido, mientras que cuando los cargamos mediante async, se cargarán nada más estar disponibles, por lo que el orden de carga no se garantiza.

Si cargas scripts con async, podría darse el caso de que un script que dependa de otro se ejecute antes de que el otro se haya ejecutado, pudiendo ocasionar errores.

Compatibilidad

A día de hoy, los atributos async y defer tiene una alta compatibilidad, salvo que te vayas a versiones de navegadores de hace diez años que apenas son ya utilizadas.

A continuación puedes ver la tabla de compatibilidad de navegadores del atributo async:

A continuación puedes ver la tabla de compatibilidad de navegadores del atributo defer:

Qué método utilizar

Definitivamente, si quieres ganar rendimiento, el mejor lugar en el que puedes colocar los scripts es en el head de la página siempre y cuando uses el atributo defer:

<script defer src="script.js"></script>

Usando defer nos aseguramos de que el código HTML se haya analizado cuando se ejecute el script y de que sus dependencias se hayan ejecutado previamente. Además, el evento domInteractive se iniciará antes, haciendo felices a tus visitantes y arañando unos puntos en el posicionamiento de tu página.

El uso de async no está mal, pero defer gana por goleada.


Avatar de Edu Lazaro

Edu Lázaro: Ingeniero técnico en informática, actualmente trabajo como desarrollador web y programador de videojuegos.

👋 Hola! Soy Edu, me encanta crear cosas y he redactado esta guía. Si te ha resultado útil, el mayor favor que me podrías hacer es el de compatirla en Twitter 😊

Si quieres conocer mis proyectos, sígueme en Twitter.

1 comentario en “Cómo Cargar Scripts Eficientemente con Async y Defer

Deja una respuesta

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

“- Hey, Doc. No tenemos suficiente carretera para ir a 140/h km. - ¿Carretera? A donde vamos, no necesitaremos carreteras.”