html - Orden de ejecución de Javascript async defer en caso de múltiples archivos en aplicaciones de una sola página
Estoy intentando mejorar el rendimiento de carga de mi página, que está implementada en EmberJS.
Estoy considerando usar asyc y defer en nuestros archivos de Javascript. Todas las demás optimizaciones ya se han realizado (mover los scripts al final de la página, agregar async y defer a las etiquetas de análisis, etc.).
Ahora, según las especificaciones de ember-cli, el index.html generado tiene dos etiquetas de script: un archivo JS de proveedor y un archivo JS de aplicación.
Si debo implementar async y defer, necesito asegurarme de que mi archivo JS de proveedor se cargue antes que mi archivo JS de aplicación, para asegurarme de que este último tenga todo el código requerido para inicializar la aplicación.
Entiendo que el orden en el que los scripts se obtienen y se analizan es diferente cuando se definen con async y defer, como se define aquí
Mi pregunta es esta:
Si tienes varios archivos JS en la misma página, ¿hay alguna forma de obtenerlos y ejecutarlos en un orden estipulado? Estoy buscando algo como callbacks/promesas en solicitudes asíncronas, pero en términos de la propia etiqueta script.
Respuesta #1
Es posible que las cosas hayan cambiado para mejor desde que se publicó esta pregunta por primera vez, pero parece que en 2019 puedes diferir tus scripts y hacer que se procesen en el orden en que las etiquetas de script están escritas en tu documento html. Agregar defer tanto a tu script vendor como a tu script main hará que se carguen en paralelo, no bloqueen el análisis del documento html y se procesen en orden al completarse el análisis del documento.
La sección 4.12.1.1 Modelo de procesamiento del documento de scripting de whatwg entra en bastante detalle que intentaré resumir aquí:
Si el tipo del script es "clásico" (no
type="module"), y el elemento tiene un atributosrc, y el elemento tiene un atributodefer, y el elemento ha sido marcado como "insertado por el parser", y el elemento no tiene un atributoasyncluego añade el elemento al final de la lista de scripts que se ejecutarán en orden tan pronto como sea posible asociada con el documento nodo del elemento script en el momento en que comenzó el algoritmo de preparación de scripts.
Revisa el enlace para obtener todos los detalles, pero esencialmente lo que parece estar diciendo es que los scripts diferidos se procesarán en el orden en que se parsean en el documento HTML.
MDN está de acuerdo:
Los scripts con el atributo
deferse ejecutarán en el orden en que aparecen en el documento.
Otro punto importante a tener en cuenta (del mismo documento de MDN):
Los scripts con el atributo
deferevitarán que el eventoDOMContentLoadedse dispare hasta que el script se haya cargado y haya terminado de evaluarse.
También vale la pena destacar que ni whatwg ni MDN dicen nada sobre colocar tu etiqueta de script en el head o al final del body del documento html. Si todos tus scripts tienen el atributo defer, se procesarán en el orden en que aparecen cuando el documento html haya completado el análisis. Colocar las etiquetas de script en el encabezado significará que comenzarán a descargarse temprano en el proceso de análisis del documento html, en lugar de más tarde, como es el caso cuando se colocan al final del body. Pero, por supuesto, eso también depende de cuántos otros recursos estés descargando desde el mismo host en paralelo.
Dejando que me desvíe un poco ahora, pero en resumen, para el mejor rendimiento sin bloqueo:
- Coloca todas tus etiquetas de script lo antes posible en el documento html
- Agréguelos en el orden en que desea que sean procesados
- Agrega el atributo
defera todos ellos (si no necesitan ser procesados de manera sincrónica o inmediatamente después de ser descargados) - Para los scripts que necesitan ser procesados tan pronto como se descarguen, agregue el atributo
async. El análisis de HTML continuará mientras el script se descarga, se pausará cuando el script haya terminado de descargarse y mientras se ejecute el script, y se reanudará una vez que el script haya terminado de ejecutarse. - Para los scripts que necesitan ser procesados tan pronto como se descarguen y que tienen efectos secundarios como modificar el DOM, no agregues
asyncodefer. El análisis de HTML se pausará mientras se descarga el script, permanecerá pausado cuando el script haya terminado de descargarse y mientras se ejecuta el script, y se reanudará una vez que el script haya terminado de ejecutarse.
Actualización julio 2020:
En Chrome, la descarga y el análisis de scripts sincrónicos (aquellos sin async o defer) han mejorado bastante. La descarga y el análisis se realizan en hilos separados, y el hilo de descarga envía el archivo al analizador mientras se descarga.
En combinación con <link rel="preload"> en tu <head>, es posible que tu archivo ya se haya descargado cuando el analizador HTML llegue a tu etiqueta <script>, lo que significa que no necesitará pausar y podrá ejecutar el script de inmediato:
La imagen de arriba se toma del video Día 2: Chrome web.dev Live 2020 - Qué hay de nuevo en V8/Javascript - la sección en la que explican las actualizaciones sobre la descarga y el análisis dura aproximadamente 4 minutos, pero vale la pena verla.
Respuesta #2
Puedo pensar en dos enfoques.
a) Haz lo que dijiste. Es decir, ten una etiqueta de script que tenga dos promesas encadenadas dentro, cada una de las cuales crea una nueva etiqueta de script, la agrega al DOM, añade una función de evento onload que sería la función resolve de la promesa y, por último, establece su atributo src a la URL del recurso. Cuando se cargue el script de la primera promesa, la segunda promesa debería ejecutarse y hacer lo mismo.
b) Tome el camino intermedio. Haga que el archivo del proveedor se coloque en la cabecera, para cargarse de manera sincrónica, y que el archivo de la aplicación esté al final del documento, para cargarse después de que todo lo demás haya terminado.
En mi opinión, la primera opción es un exceso.
EDITAR: Ejemplo para a)
<script>
var p = new Promise(function(resolve, reject) {
var scriptTag = document.createElement('script');
document.head.appendChild(scriptTag);
scriptTag.onload = resolve;
scriptTag.src = 'URL_to_vendor_file';
});
p.then(function() {
var scriptTag = document.createElement('script');
document.head.appendChild(scriptTag);
scriptTag.src = 'URL_to_application_file';
};
</script>
Nota: El ejemplo anterior puede escribirse sin el uso de promesas

