Carga asíncrona de Javascript

Con páginas, y aplicaciones web, cada vez más pesadas, nos enfrentamos a un importante reto, que excede las capacidades iniciales de ECMAScript.

Me estoy refiriendo a la velocidad de visualización de las páginas. Por defecto, una página sólamente se renderiza cuando todas sus dependencias están disponibles, por lo que con tamaños que aumentan, cada vez es más complicado.

Cabe recordar, que cuando hacemos una inclusión de un archivo JS externo en nuestra página, lo que ocurre es que el navegador solicita la el archivo, lo ejecuta, y entonces continúa la carga de la página principal. Es lo que se llama Blocking Javascript, y que es como digo, el comportamiento por defecto, cuando hacemos algo como esto:

No se trata de que carguen al completo de manera más rápida, ese sería otro tema, sino que solamente lo parezca, haciendo que la mayoría de contenido se visualice lo antes posible.

Antiguamente lo que se hacía, era mover todos los scripts que no era necesario tener durante la carga de la página, al final del body, así al menos, el HTML se visualizaba, y luego se ponían a cargar los Javascript.

Después, se empezó a hacer evaluando el include con el propio Javascript, de manera que hasta que terminaba de parsearse la página, no se generaba el include, y por tanto no se cargaba, de esta forma:

document.write('< ' + 'script src="miscript" type="text/javascript">< ' + '/script>');

Después se hacía esto mismo, pero creando el elemento en el DOM, en vez de con document.write, que causaba bastante deterioro en el rendimiento de JIT. Así:

function LoadScript(src, callback)
{
	var s, r, t;
	r = false;
	s = document.createElement('script');
	s.type = 'text/javascript';
	s.src = src;
	s.onload = s.onreadystatechange = function()
	{
		if (!r && (!this.readyState || this.readyState == 'complete'))
		{
			r = true;
			callback();
		}
	};
	t = document.getElementsByTagName('script')[0];
	t.parentNode.insertBefore(s, t);
}

Finalmente, aparecieron los atributos defer y async, que ayudaban a implentar esa carga no bloqueante de scripts (asíncrona), de forma nativa, claro está en los navegadores que la soportan.

En el caso de defer, es más compatible (IE 6, Firefox 3.5, Chrome 8, Safari 5, Opera 15), pero limita la carga y ejecución del código incluido, a cuando ha terminado de parsearse la página:

Se puede usar async, que es más reciente (IE 10, Firefox 3.6, Chrome 8, Safari 5.1, Opera 15), y que permite que el código se cargue y ejecute en paralelo con la carga y renderizado de la página:

Claro que ninguno de estos sistemas es mágicos, si por ejemplo el código que cargamos depende de que determinados elementos del DOM estén cargados, no funcionará la asincronía, y puede que tampoco lo haga el evento onload, ya que el HTML puede estar cargado, pero el resto de dependencias aún no, de modo que no nos quedará más opción que interceptar los diferentes eventos soportados por diferentes navegadores, onload, onreadystatechange, load o DOMContentLoaded.

Parece que como en casi todo, mejorar el rendimiento nunca es fácil, ¡pero si divertido!

A este respecto, espero que hayáis notado alguna mejoría reciente en la carga de mi blog.

8 comentarios en “Carga asíncrona de Javascript”

  1. Muy buen post, magníficos los ejemplos y muy bien explicados. De hecho lo he imprimido para guardar, porque mi experiencia en este aspecto con javascript siempre ha sido muy básica: mete lo menos posible y correrá más rápido 😀

    Me sorprende que no sean capturas sino que las líneas de código estén identadas e incluso con color y atributos, ¿con qué editor escribes los posts? No creo que sea con el propio de wordpress, cuando lo usaba no recuerdo que tuviese todas esas cosas.

  2. Javier Gutiérrez Chamorro (Guti)

    Muchas gracias amigo.

    El coloreado de código, es efectivamente un plugin de WordPress (WP-Syntax), que soporta diferentes lenguajes, algo que me resulta muy útil.

    Si quieres cotillear, detallé algunos de los que usaba aquí.

  3. Hola,
    Muy interesante el artículo.
    Una pregunta, ¿esto puede hacerse también con scripts externos a mi web?
    En mi caso tengo un script de una pasarela de pago (gumroad).
    Si incluyo este script en la home mi web tarda +10 segundos en cargar, si lo elimino sólo 3.
    ¿Esta solución vale también para los scripts externos?

    Gracias

  4. Javier Gutiérrez Chamorro (Guti)

    Muchas gracias Esther. Técnicamente, no hay ningún impedimento para hacerlo con scripts externos. Es exactamente igual. La diferencia estriba en que como digo, el script debe soportar la carga asíncrona o retrasada, ya que si no lo hace, puede no funcionar correctamente. En ese caso, siendo el script externo, no tienes control sobre él para modificarlo.

    Mi recomendación es que hagas la prueba, si funciona, habrás reducido el tiempo de carga debido al bloqueo de ese script de 10 a 3 segundos, que es una mejora notable. Si no funciona, deberás ponerte en contacto con sus desarrolladores para que den soporte a dicha característica.

Deja un comentario