viernes, 7 de marzo de 2014

Atributos 'defer' y 'async' en <script>

Cuando incluimos un script en una página mediante las etiquetas <script></script>, éste se cargará de forma síncrona bloqueando el resto de la página hasta que se haya ejecutado. Ocurre lo mismo tanto si el código es inline como si es un archivo remoto. En la entrada "Los scripts externos pueden bloquear tu web" explicaba en detalle las diferencias entre carga síncrona (bloqueante) y carga asíncrona (no bloqueante).

Aunque la presentación está bloqueada, los navegadores modernos buscan otros ficheros externos que tengan que cargar y lanzan la petición (js, imágenes, css). La descarga sí se inicia, pero toda la presentación de elementos está bloqueada hasta que se ejecuten. Por esto se recomienda colocar los scripts al final, para que todo el HTML se haya presentado ya en pantalla y el usuario tenga una sensación de carga rápida.

Hay dos atributos que podemos incluir en la etiqueta <script> para modificar este comportamiento y forzar la carga asíncrona (no bloqueante):

  • defer <script defer src="myFile.js"></script>
  • async (HTML5) <script async src="myFile.js"></script>

Los dos son atributos booleanos y tienen un funcionamiento similar. La diferencia principal es el momento en que se ejecuta el código descargado. En el caso de async se ejecuta en cuanto la descarga se ha completado, mientras que en el caso de defer se ejecuta cuando se ha terminado de parsear toda la página.

Atributo 'defer'

Microsoft introdujo defer en el Internet Explorer 4 para evitar que los scripts bloquearan la presentación del resto de la página si no era necesario (si no incluian elementos nuevos).

Se utiliza de la siguiente manera:

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

Se estandarizó en HTML4 con el siguiente texto:

"defer
Este atributo booleano proporciona una indicación para el UA [browser] de que el script no va a generar ningún contenido en el documento (p.ej. el javascript no contiene document.write) y, por lo tanto, el UA puede continuar con el parseo y presentación"

En HTML5 se especifica de la siguiente forma:

async y defer son atributos booleanos que indican cómo se deben ejecutar los scripts. Los atributos defer y async no deben especificarse si no existe el atributo src.

[...]si el atributo defer está presente [sin el atributo sync] el script se ejecuta cuando la página se ha terminado de parsear.

Es decir, este atributo se utiliza únicamente para scripts externos, se ignora para scripts inline. Algunas versiones de IE (IE<10) no respetan esta indicación del estandar y pueden aplicar defer a código en línea.

Los scripts se ejecutan justo antes de lanzarse el evento DOMContentLoaded.

Además, en otro punto del estandar se indica que los scripts con defer deben ejecutarse en el orden en que se especificaron en la página.

Atributo 'async'

async se introduce en HTML5 para indicar que el script debe cargarse de forma asíncrona, sin bloquear la presentación del resto de contenido, y ejecutarse inmediatamente cuando se haya descargado. El orden de ejecución no se garantiza.

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

La especificación de HTML5 indica lo siguiente:

async y defer son atributos booleanos que indican cómo se deben ejecutar los scripts. Los atributos defer y async no deben especificarse si no existe el atributo src.

[...] Si el atributo async está presente el script se ejecuta de forma asincrona tan pronto como esté disponible.

En este caso el evento DOMContentLoaded también esperará a que el script se ejecute.

Los dos juntos: 'async' + 'defer'

Cuando los dos atributos aparecen juntos, async prevalece sobre defer si el navegador lo soporta. defer puede añadirse como fallback para navegadores antiguos:

El atributo defer puede especificarse incluso si el atributo async está presente, para que los navegadores antiguos que sólo soportan defer utilicen esa opción en vez de la descarga síncrona bloqueante que sería la opción por defecto.

Debido a las diferentes implementaciones es mejor evitar dependencias y no asumir, en ningún caso, que los scripts se ejecutarán en orden ( con defer). Evitaremos problemas si consideramos siempre que se descargarán de forma asíncrona pero no se garantiza el orden de ejecución.

Inyectando un <script> dinámicamente

Otra opción muy usada para cargar un script de forma asíncrona es inyectar la etiqueta <script> dinámicamente, con javascript:

    

var script = document.createElement("script");
script.src = "/path/to/script.js";
document.getElementsByTagName("head")[0].appendChild(script);

Los <script> que se añaden dinámicamente al documento son asíncronos por defecto, no bloquean la presentación de la página y se ejecutan en cuanto se han descargado. El mismo comportamiento que con el atributo async.

En este caso el evento DOMContentLoaded no espera a que el script se haya ejecutado.

A veces podemos encontrar también que se incluye el atributo *async* en este tipo de scripts:

var script = document.createElement("script");
script.src = "/path/to/script.js";    
script.async = true;               //set async attribute 
document.getElementsByTagName("head")[0].appendChild(script);

Es únicamente para los navegadores Firefox 3.6 y Opera, que no tienen el comportamiento asíncrono por defecto para *scripts* inyectados pero soportarn el atributo *async*.

Fuentes:

W3C HTML5 Scripting
HTML: The Markup Language (an HTML language reference)
Deep dive into the murky waters of script loading

6 comentarios: