Pensamiento asíncrono | Programar Plus

Aquí está el problema: cuando carga JavaScript de un tercero, debe hacerlo de forma asíncrona. Es posible que también desee cargar sus propios scripts de forma asincrónica, pero para este artículo centrémonos en terceros.

Hay dos razones para esto:

  1. Si el tercero se cae o es lento, su página no se detendrá al intentar cargar ese recurso.
  2. Puede acelerar la carga de la página.

En Wufoo, simplemente cambiamos a un fragmento de código incrustado asíncrono. Ahora se recomienda que los usuarios que crean formularios con Wufoo y desean insertarlos en su sitio lo utilicen. Lo hicimos exactamente por las razones anteriores. Es lo más responsable de un servicio web que pide a las personas que se vinculen a recursos en ese sitio de servicios.

Exploremos todo este asunto asincrónico.

Uhm. ¿Qué?

Hay un poco de terminología involucrada aquí que nos ayudará a comprender el término general “asincrónico”.

“Bloqueo del analizador” – El navegador lee su HTML y cuando se trata de un <script> descarga todo el recurso antes de continuar con el análisis. Esto definitivamente ralentiza la carga de la página, especialmente si el guión está en la cabecera o encima de cualquier otro elemento visual. Esto es cierto tanto en navegadores antiguos como en navegadores modernos si no usa el async atributo (más sobre esto más adelante). De los documentos de MDN: “En navegadores más antiguos que no son compatibles con el async atributo, los scripts insertados por el analizador bloquean el analizador…”

Para evitar el bloqueo problemático del analizador, los scripts se pueden “script insertado” (es decir, inserte otro script con JavaScript) que luego los obliga a ejecutarse de forma asincrónica (excepto en Opera o Firefox anterior a 4.0).

“Bloqueo de recursos” – Mientras se descarga un script, puede evitar que otros recursos se descarguen al mismo tiempo. IE 6 y 7 hacen esto, solo permiten descargar un script a la vez y nada más. IE 8 y Safari 4 permiten la descarga de varios scripts en paralelo, pero bloquean cualquier otro recurso (referencia).

Idealmente, luchamos contra estos dos problemas y aceleramos la velocidad de carga de la página (tanto real como percibida).

El estilo HTML5

Hay un async atributo para el script etiqueta en HTML5 (especificación). Ejemplo:

<script async src="https://third-party.com/resource.js"></script>

El soporte del navegador es Firefox 3.6+, IE 10+, Chrome 2+, Safari 5+, iOS 5+, Android 3+. Aún no hay soporte para Opera.

Si va a cargar un script directamente así, usar este atributo probablemente sea una buena idea. Previene el bloqueo del analizador. Estos navegadores más nuevos no tienen grandes problemas con el bloqueo de recursos, pero el tema del analizador es un gran problema. No estamos usando esto en Wufoo porque necesitamos un soporte de navegador mucho más profundo que eso.

La forma asincrónica clásica

Este es el patrón básico de carga de secuencias de comandos asíncronas que le brindará la mayor compatibilidad con el navegador que necesite:

<script>
  var resource = document.createElement('script'); 
  resource.src = "https://third-party.com/resource.js";
  var script = document.getElementsByTagName('script')[0];
  script.parentNode.insertBefore(resource, script);
</script>

Aquí hay una versión más eficiente con un contenedor alrededor de esas variables (crédito Mathias Bynens):

(function(d, t) {
    var g = d.createElement
        s = d.getElementsByTagName
    g.src="https://third-party.com/resource.js";
    s.parentNode.insertBefore(g, s);
}(document, 'script'));

Actualizar: Tenga en cuenta que esto ya no se recomienda! La era que hizo de este un patrón útil ha terminado, y en su mayoría solo deberías usar el async atributo.

Redes publicitarias

BuySellAds es una red publicitaria que fue una de las primeras en publicar anuncios de forma asíncrona. Este es su patrón:

<script type="text/javascript">
(function(){
  var bsa = document.createElement('script');
     bsa.type="text/javascript";
     bsa.async = true;
     bsa.src="https://s3.buysellads.com/ac/bsa.js";
  (document.getElementsByTagName('head')[0]||document.getElementsByTagName('body')[0]).appendChild(bsa);
})();
</script>

Un par de cosas a tener en cuenta aquí:

  • ellos estan poniendo el async atributo del script a verdadero antes de agregarlo. Esto es útil exclusivamente para Firefox 3.6, que es el único navegador que no lo hace de forma predeterminada. Esto probablemente se puede omitir en la mayoría de los escenarios. Configuración del guión type definitivamente no es necesario.
  • Como en el patrón simple anterior, el src se establece mediante una URL relativa al protocolo. Esta es una forma muy útil de cargar el script desde HTTP o HTTPS, según la página que lo solicitó. Es absolutamente necesario que hagamos esto en Wufoo, pero lamentablemente descubrimos que arroja un error en IE 6 con la configuración de seguridad predeterminada. Si no necesita compatibilidad con IE 6, utilícela.
  • Están agregando el guión a la cabeza o al cuerpo, lo que se encuentre primero. Es una forma totalmente buena de hacerlo, pero igual de seguro es buscar un elemento de secuencia de comandos, ya que el código en sí es un elemento de secuencia de comandos.

El Deck usa un patrón diferente:

<script type="text/javascript">
//<![CDATA[
(function(id) {
 document.write('<script type="text/javascript" src="' +
   'http://connect.decknetwork.net/deck' + id + '_js.php?' +
   (new Date().getTime()) + '"></' + 'script>');
})("DF");
//]]>
</script>

Estoy bastante seguro de que esto todavía califica como asincrónico porque el recurso que están cargando es un script inyectado, lo que significa que no se bloqueará.

¿Qué sucede si necesita una devolución de llamada?

A veces, necesita cargar un script de terceros, luego, una vez que se carga ese script, active un código personalizado. Ese código personalizado probablemente llama a alguna función definida en el script de terceros con datos que son exclusivos de una página específica.

<script src="https://third-party.com/resource.js"></script>
<script>
  doSomethingFancy('chriscoyier');
</script>

Typekit se encuentra en esta situación. Debe cargar el JavaScript de Typekit y luego iniciarlo. Typekit en realidad está aprovechando el hecho de que los scripts bloquean el analizador. Si su página se retiene hasta que se carga su secuencia de comandos, no verá el “FOUT” (Flash Of Unstyled Text), generalmente solo problemático en Firefox, pero también problemático en Typekit donde los recursos @font-face se cargan a través de JavaScript .

<script type="text/javascript" src="https://use.typekit.com/abc1def.js"></script>
<script type="text/javascript">try{Typekit.load();}catch(e){}</script>

Esto es inteligente, pero es un poco peligroso. Si Typekit estuviera inactivo o lento: “Lo que alguna vez fue un retraso deseable en el renderizado para ocultar el FOUT se convierte en un problema grave cuando el script tarda más de unos segundos en cargarse”. (referencia).

Esta es una forma de cargar el estilo asíncrono de Typekit:

<script type="text/javascript">
  TypekitConfig = {
    kitId: 'abc1def'
  };
  (function() {
    var tk = document.createElement('script');
    tk.src="https://use.typekit.com/" + TypekitConfig.kitId + '.js';
    tk.type="text/javascript";
    tk.async="true";
    tk.onload = tk.onreadystatechange = function() {
      var rs = this.readyState;
      if (rs && rs != 'complete' && rs != 'loaded') return;
      try { Typekit.load(TypekitConfig); } catch (e) {}
    };
    var s = document.getElementsByTagName('script')[0];
    s.parentNode.insertBefore(tk, s);
  })();
</script>

Hay una gran cantidad de código retorcido allí para manejar la devolución de llamada de carga. Desafortunadamente, así es como funciona hacer que las devoluciones de llamada funcionen con un soporte profundo del navegador. Tenga cuidado, el uso de este patrón en realidad trae de vuelta el problema de FOUT. Si desea sincronizarse con Typekit y tener una experiencia tan buena como la que tiene normalmente, lea su publicación, que cubre algunos eventos de fuente y manipulación inteligente de nombres de clase.

jQuery y otros cargadores de scripts

Si ya está utilizando jQuery, cargar un script de terceros y obtener una devolución de llamada cuando esté listo es bastante fácil:

$.ajax({
  url: 'https://third-party.com/resource.js',
  dataType: 'script',
  cache: true, // otherwise will get fresh copy every page load
  success: function() {
    // script loaded, do stuff!
  }
}

Estoy seguro de que otras bibliotecas tienen capacidades similares. Es lo clásico en lo que las bibliotecas de JavaScript son buenas para ayudar. Consulte también getScript, que podría ser un poco más breve.

Si no está utilizando una biblioteca y le preocupa el tamaño del archivo, YepNope es un cargador de scripts súper pequeño que también puede ayudar. Su uso ideal es realizar una prueba para ver si necesita cargar el script o no, pero también tiene métodos directos:

yepnope.injectJs("https://third-party.com/resource.js", function () {
  // script loaded, do stuff!
});

Relevante: el artículo de Max Wheeler Loading Typekit Asynchronously with YepNope.

¿Realmente necesitas una devolución de llamada?

En Wufoo, pensamos que sí, necesitamos una devolución de llamada. Necesitamos cargar el recurso JavaScript de incrustación de formularios, luego llamar a una función con todos los detalles del formulario del usuario. Así es como solíamos hacerlo:

<script type="text/javascript">var host = (("https:" == document.location.protocol) ? "https://secure." : "http://");document.write(unescape("%3Cscript src="" + host + "wufoo.com/scripts/embed/form.js" type="text/javascript"%3E%3C/script%3E"));</script><br />
<script type="text/javascript">
var z7p9p1 = new WufooForm();
z7p9p1.initialize({
'userName':'chriscoyier', 
'formHash':'z7p9p1', 
'autoResize':true,
'height':'546', 
'ssl':true});
z7p9p1.display();
</script>

Ese conjunto de pares clave/valor es útil para que un usuario pueda ver, cambiar y agregar cosas. Jugamos con algunas formas en que podríamos mantener eso, pero pasar esos datos como parte de la URL al llamar al script. El script de nuestro lado realmente sería PHP y sería capaz de $_GET Los valores. Eso evitaría tener que lidiar con todo el feo código de devolución de llamada en un patrón asíncrono. Posiblemente algo como:

script.src="https://wufoo.com/form.js?data=" + JSON.stringify(options);

Pero finalmente, decidimos no hacerlo. El código de devolución de llamada no es tan malo, JSON de cadena no tiene un soporte de navegador muy profundo (y no es práctico incluir un código de copia y pegado de policompletar), y tener nuestro form.js ideal en caché.

Redes sociales

Los botones de redes sociales son un caso clásico de necesidad de JavaScript de terceros en su página. Curiosamente, los tres jugadores más importantes ya proporcionan su código en un patrón asíncrono.

Facebook

<div id="fb-root"></div>
<script>(function(d, s, id) {
  var js, fjs = d.getElementsByTagName(s)[0];
  if (d.getElementById(id)) return;
  js = d.createElement(s); js.id = id;
  js.src = "https://connect.facebook.net/en_US/all.js#xfbml=1&appId=200103733347528";
  fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>

Gorjeo

<a href="https://twitter.com/share" class="twitter-share-button">Tweet</a>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="https://platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>

Google Mas

<g:plusone annotation="inline"></g:plusone>
<script type="text/javascript">
  (function() {
    var po = document.createElement('script'); po.type="text/javascript"; po.async = true;
    po.src="https://apis.google.com/js/plusone.js";
    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
  })();
</script>

Limpiando la casa

Todos los anteriores son muy similares pero ligeramente diferentes. Ponerlos todos tal como están en una página puede hacer llorar a los puristas del código como nosotros. Nicholas Gallagher tiene una forma realmente limpia y eficiente de ponerlos todos juntos:

(function(doc, script) {
    var js, 
        fjs = doc.getElementsByTagName(script)[0],
        add = function(url, id) {
            if (doc.getElementById(id)) {return;}
            js = doc.createElement(script);
            js.src = url;
            id && (js.id = id);
            fjs.parentNode.insertBefore(js, fjs);
        };

    // Google Analytics
    add('https:://ssl.google-analytics.com/ga.js', 'ga');
    // Google+ button
    add('https://apis.google.com/js/plusone.js');
    // Facebook SDK
    add('https://connect.facebook.net/en_US/all.js', 'facebook-jssdk');
    // Twitter SDK
    add('https://platform.twitter.com/widgets.js', 'twitter-wjs');
}(document, 'script'));

Tratar con los CMS

WordPress es jodidamente enorme. También lo son todos los demás CMS principales. No se pueden ignorar cuando usted es un tercero que ofrece código JavaScript de copiar y pegar. La clave, por supuesto, es probar. Lo más importante es no incluir saltos de línea dobles dentro del código. Me gusta:

<script type="text/javascript">
var s = d.createElement

   foo: bar

}
// The line break space above is bad!
</script>

Eso puede verse bien y limpio, pero el comportamiento de “autop” de WordPress insertará etiquetas de párrafo alrededor de varias partes de eso, lo que por supuesto evita que el script se ejecute como se esperaba.

Fragmento final de Wufoo

Esto es con lo que terminamos:

<div id="wufoo-z7w3m7">
Fill out my <a href="http://examples.wufoo.com/forms/z7w3m7">online form</a>.
</div>
<script type="text/javascript">var z7w3m7;(function(d, t) {
var s = d.createElement
'userName':'examples', 
'formHash':'z7w3m7', 
'autoResize':true,
'height':'260',
'async':true,
'header':'show', 
'ssl':true};
s.src="https://wufoo.com/scripts/embed/form.js";
s.onload = s.onreadystatechange = function() {
var rs = this.readyState; if (rs) if (rs != 'complete') if (rs != 'loaded') return;
try { z7w3m7 = new WufooForm();z7w3m7.initialize(options);z7w3m7.display(); } catch (e) {}};
var scr = d.getElementsByTagName
})(document, 'script');</script>

Honestamente, el “tamaño” del fragmento era una preocupación. Cincuenta líneas es demasiado para algo como esto. Está en 19 ahora, que es más de lo que teníamos, pero es aceptable. Muchas de esas líneas son el objeto de opciones, que podríamos aplastar en menos líneas, pero es mejor tenerlas en líneas separadas para leer/cambiar.

Todavía debemos ser compatibles con IE 6, por lo que, lamentablemente, no hay URL relativas al protocolo para nosotros. estamos usando el location.protocol prueba.

Es un poco más grande que su fragmento asíncrono “promedio” (si es que existe un fragmento promedio), pero está bien. Tiene bastante trabajo por hacer y lo hace bien.

Hablamos de algunas de las ventajas en la publicación del blog del anuncio. Mi favorito es que ahora puede mover la secuencia de comandos a cualquier lugar que desee, no es necesario que esté exactamente donde desea que aparezca el formulario como solía ser.

Cascadas

Si está interesado en realizar algunas pruebas sobre la carga de recursos, es particularmente útil observar las cascadas de recursos. Las herramientas modernas de desarrollo web tienen esto incorporado, como la pestaña “Red” en el inspector web o la pestaña “Red” de Firebug. Pero las herramientas de desarrollo de la vieja escuela en IE 6-8 no ofrecen esa información. Afortunadamente, el sitio web Web Page Test sí lo hace (es un poco feo pero es genial).

Mientras hacía algunas pruebas para el fragmento de Wufoo en IE 6, pude probar que nuestra nueva forma no bloqueaba y la anterior bloqueaba:

Bien, eso es todo lo que tengo. Me siento un poco raro escribiendo sobre todo esto porque todo es bastante nuevo para mí y siento que estoy lejos de ser un experto. Así que siéntete libre de corregirme en cualquier cosa o compartir tus propias experiencias asincrónicas.