2010-07-14 21 views
59

¿Es posible invocar un archivo .js sincrónicamente y luego utilizarlo inmediatamente después?document.createElement ("script") sincrónicamente

<script type="text/javascript"> 
    var head = document.getElementsByTagName('head').item(0); 
    var script = document.createElement('script'); 
    script.setAttribute('type', 'text/javascript'); 
    script.setAttribute('src', 'http://mysite/my.js'); 
    head.appendChild(script); 

    myFunction(); // Fails because it hasn't loaded from my.js yet. 

    window.onload = function() { 
     // Works most of the time but not all of the time. 
     // Especially if my.js injects another script that contains myFunction(). 
     myFunction(); 
    }; 
</script> 

Esto se simplifica. En mi implementación, las cosas de createElement están en una función. Pensé en agregar algo a la función que pudiera verificar si una determinada variable se instanciaba antes de devolver el control. Pero aún existe el problema de qué hacer al incluir js de otro sitio sobre el que no tengo control.

¿Pensamientos?

Editar:

He aceptado la mejor respuesta por el momento, ya que da una buena explicación de lo que está pasando. Pero si alguien tiene alguna sugerencia sobre cómo mejorar esto, estoy abierto a ellos. Aquí hay un ejemplo de lo que me gustaría hacer.

// Include() is a custom function to import js. 
Include('my1.js'); 
Include('my2.js'); 

myFunc1('blarg'); 
myFunc2('bleet'); 

sólo quiero evitar tener que conocer el funcionamiento interno demasiado y acaba de ser capaz de decir: "Me gustaría utilizar este módulo, y ahora voy a utilizar un código de él."

+0

no he encontrado la manera de hacer referencias al mismo valor sin crear una matriz (para el recuento). De lo contrario, creo que se explica por sí mismo (cuando todo está cargado, 'eval()' cada archivo en el orden dado, de lo contrario simplemente almacena la respuesta). –

Respuesta

92

Usted puede crear su elemento <script> con una "onload" manipulador, y que se llamará cuando el guión se ha cargado y evaluado por el navegador .

var script = document.createElement('script'); 
script.onload = function() { 
    alert("Script loaded and ready"); 
}; 
script.src = "http://whatever.com/the/script.js"; 
document.getElementsByTagName('head')[0].appendChild(script); 

No puede hacerlo sincrónicamente.

edición — se ha señalado que, como era de esperar, IE no se dispara un evento de "carga" en la <script> etiquetas se cargan/evaluados. Por lo tanto, supongo que lo siguiente que debe hacer es buscar el script con XMLHttpRequest y luego eval(). (O, supongo, rellenar el texto en una etiqueta <script> agrega;. El entorno de ejecución de eval() se ve afectado por el ámbito local, por lo que no necesariamente va a hacer lo que usted quiere que haga)

ediciónA partir de principios de 2013, recomendaría encarecidamente buscar en una herramienta de carga de scripts más robusta como Requirejs. Hay muchos casos especiales de los que preocuparse. Para situaciones realmente simples, hay yepnope, que ahora está integrado en Modernizr.

+3

lamentablemente no es un navegador cruzado. – galambalazs

+61

¿De verdad? ¿Quién no activa un evento de "carga" cuando se carga un script? ** Espera ** - no me digas. – Pointy

+1

@Pointy Resolví este problema usando XMLHttpRequest y luego 'eval()'. Sin embargo, la depuración es una pesadilla b/c el mensaje de error informa que aparece la línea 'eval()', no el error real – puk

6

asíncrono programación es un poco más complicada porque la consecuencia de hacer una solicitud se encapsula en una función en lugar de seguir el estado de la solicitud. Pero el comportamiento en tiempo real que las experiencias de los usuarios pueden ser significativamente mejor porque no van a ver a un servidor de red lento o débil que el navegador a actuar como si se hubiera estrellado.La programación sincrónica es irrespetuosa y no debe emplearse en aplicaciones que son utilizadas por personas.

Douglas Crockford(YUI Blog)

bien, hebilla de sus asientos, porque va a ser un viaje lleno de baches. Cada vez más personas preguntan sobre cómo cargar scripts dinámicamente a través de javascript, parece ser un tema candente.

Las principales razones para que esto se hizo tan popular son:

  • modularidad del lado del cliente
  • más fácil de dependencia de gestión
  • manejo de errores
  • ventajas de rendimiento

Sobre modularidad : es obvio que la gestión del lado del cliente depende ncies deben manejarse en el lado del cliente. Si se necesita un determinado objeto, módulo o biblioteca, solo lo solicitamos y lo cargamos dinámicamente.

Error al manejar: si un recurso falla, todavía tenemos la posibilidad de bloquear solo las partes que dependen del guión afectado, o tal vez incluso darle otra oportunidad con algún retraso.

El rendimiento se ha convertido en una ventaja competitiva entre los sitios web, ahora es un factor de clasificación de búsqueda. Lo que las secuencias de comandos dinámicas pueden hacer es imitar el comportamiento asincrónico en comparación con la forma predeterminada de bloqueo de cómo los navegadores manejan las secuencias de comandos. Scripts bloquear otros recursos, secuencias de comandos bloque más análisis del documento HTML, secuencias de comandos bloquean la interfaz de usuario. Ahora, con las etiquetas de scripts dinámicos y sus alternativas entre navegadores, puede realizar solicitudes asincrónicas reales y ejecutar código dependiente solo cuando estén disponibles. Sus scripts se cargarán en paralelo incluso con otros recursos y la representación será perfecta.

La razón por la que algunas personas se adhieren a scripts sincrónicos es porque están acostumbrados. Ellos piensan que es la manera predeterminada, es la manera más fácil, y algunos incluso pueden pensar que es la única manera.

Pero lo único que deberíamos tener en cuenta cuando esto deba decidirse con respecto al diseño de una aplicación es la experiencia del usuario final . Y en esta área asincrónico no se puede superar. El usuario obtiene respuestas inmediatas (o decir promesas), y una promesa siempre es mejor que nada. Una pantalla en blanco asusta a las personas. Los desarrolladores no deben ser perezosos para mejorar el rendimiento percibido.

Y, finalmente, algunas palabras sobre el lado sucio.Lo que debe hacer con el fin de conseguir que funcione en todos los navegadores:

  1. aprender a pensar de forma asíncrona
  2. organizar el código para ser modular
  3. organizar el código para controlar los errores y casos extremos, así
  4. mejorar progresivamente
  5. siempre cuidar de la cantidad correcta de retroalimentación
+0

También echa un vistazo a LABjs que trata con estas cosas para ti. http://labjs.com/ – Pointy

+0

Gracias, Galam. Creo que debería haber sido más claro. Esperaba que esto fuera asincrónico al final. Solo quiero una forma de acceder que tenga sentido lógico para el programador. Quería evitar cosas como: Importar ("paquete.mod1", función() { // hacer cosas con mod1 }); Importar ("paquete.mod2", función() { // hacer cosas con mod2 }); He echado un vistazo a su guión y sus labjs y, aunque es bueno, parece ser más complejo para mis necesidades. Pensé que podría haber una manera más simple y quería evitar incorporar dependencias adicionales. –

+1

Te perdiste el objetivo de mi publicación. Se trata de los usuarios. Esta debería ser tu primera prioridad. Todo lo demás es secundario. – galambalazs

23

Esto no es bonito, pero funciona:

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

<script type="text/javascript"> 
    functionFromOther(); 
</script> 

O

<script type="text/javascript"> 
    document.write('<script type="text/javascript" src="other.js"></script>'); 
    window.onload = function() { 
    functionFromOther(); 
    }; 
</script> 

La secuencia de comandos debe estar incluido en una etiqueta separada <script> o antes window.onload().

Esto no funcionará:

<script type="text/javascript"> 
    document.write('<script type="text/javascript" src="other.js"></script>'); 
    functionFromOther(); // Error 
</script> 

El mismo se puede hacer con la creación de un nodo, como lo hizo puntiagudo, pero sólo en FF. No tiene garantía cuando la secuencia de comandos estará lista en otros navegadores.

Siendo un purista de XML Realmente odio esto. Pero funciona de manera predecible. Puedes envolver fácilmente esos feos document.write() s para que no tengas que mirarlos. Incluso podría hacer pruebas y crear un nodo y anexarlo, luego volver al document.write().

+0

¿Estás seguro de que tu primer fragmento de código funciona en todos los navegadores? –

+0

@BogdanGusiev No estoy 100% seguro. Probé en IE 8, (las versiones actuales de) Firefox y Chrome. Es probable que esto no funcione con doctypes XHTML que se sirven como tipo de contenido 'application/xhtml + xml'. –

+1

Desafortunadamente, las etiquetas de guiones no se pueden usar en archivos JS. – Clem

0

Irónicamente, tengo lo que quieres, pero quiero algo más parecido a lo que tienes.

Estoy cargando cosas en forma dinámica y de forma asíncrona, pero con un load devolución de llamada como tal (usando dojo y xmlhtpprequest)

dojo.xhrGet({ 
    url: 'getCode.php', 
    handleAs: "javascript", 
    content : { 
    module : 'my.js' 
    }, 
    load: function() { 
    myFunc1('blarg'); 
    }, 
    error: function(errorMessage) { 
    console.error(errorMessage); 
    } 
}); 

Para una explicación más detallada, véase here

El problema es que en alguna parte a lo largo de la línea, el código es evadido, y si hay algún problema con su código, la declaración console.error(errorMessage); indicará la línea donde está eval(), no el error real. . Esta es tal un gran problema que en realidad estoy tratando de convertir de nuevo a <script> declaraciones (ver here

+0

Dato curioso: yo también tengo Volví a las etiquetas '

1

tenía el siguiente problema (s) con las respuestas existentes para esta pregunta (y variaciones de esta cuestión en otros hilos stackoverflow):

  • Ninguno de el código cargado fue depurable
  • Muchas de las soluciones requerían devoluciones de llamada para saber cuándo se terminó la carga en lugar de bloqueo real, lo que significa que obtendría errores de ejecución de llamar inmediatamente al código cargado (es decir, cargar).

O, ligeramente más exactamente:

  • Ninguno del código cargado era depurable (excepto desde el bloque de etiqueta de script HTML, si y sólo si la solución se añadió una serie de elementos de secuencia de comandos al DOM, y nunca como scripts individuales visibles.) => Dado el número de scripts que tengo que cargar (y depurar), esto era inaceptable.
  • Las soluciones que usaban eventos 'onreadystatechange' o 'onload' no se podían bloquear, lo cual era un gran problema ya que el código originalmente cargaba scripts dinámicos de forma síncrona usando 'require ([filename,' dojo/domReady ']); y estaba quitando el dojo.

Mi solución final, que carga el guión antes de regresar, y tiene todas las secuencias de comandos adecuadamente accesibles en el depurador (para Chrome al menos) es la siguiente:

ADVERTENCIA: El siguiente código debe utilizarse PROBABLEMENTE solo en modo 'desarrollo'.(Para el modo de "liberación", recomiendo el empaquetamiento previo y la minificación SIN carga dinámica de scripts, o al menos sin eval).

//Code User TODO: you must create and set your own 'noEval' variable 

require = function require(inFileName) 
{ 
    var aRequest 
     ,aScript 
     ,aScriptSource 
     ; 

    //setup the full relative filename 
    inFileName = 
     window.location.protocol + '//' 
     + window.location.host + '/' 
     + inFileName; 

    //synchronously get the code 
    aRequest = new XMLHttpRequest(); 
    aRequest.open('GET', inFileName, false); 
    aRequest.send(); 

    //set the returned script text while adding special comment to auto include in debugger source listing: 
    aScriptSource = aRequest.responseText + '\n////# sourceURL=' + inFileName + '\n'; 

    if(noEval)//<== **TODO: Provide + set condition variable yourself!!!!** 
    { 
     //create a dom element to hold the code 
     aScript = document.createElement('script'); 
     aScript.type = 'text/javascript'; 

     //set the script tag text, including the debugger id at the end!! 
     aScript.text = aScriptSource; 

     //append the code to the dom 
     document.getElementsByTagName('body')[0].appendChild(aScript); 
    } 
    else 
    { 
     eval(aScriptSource); 
    } 
}; 
3

Las respuestas anteriores me señaló en la dirección correcta. Aquí hay una versión genérica de lo que me de trabajo:

var script = document.createElement('script'); 
    script.src = 'http://' + location.hostname + '/module'; 
    script.addEventListener('load', postLoadFunction); 
    document.head.appendChild(script); 

    function postLoadFunction() { 
    // add module dependent code here 
    }  
+0

¿Cuándo se llama a 'postLoadFunction()'? –

+1

@JoshJohnson 'script.addEventListener ('load', postLoadFunction);' significa que se requiere postLoadFunction en la carga de scripts. – Eric

2
function include(file){ 
return new Promise(function(resolve, reject){ 
     var script = document.createElement('script'); 
     script.src = file; 
     script.type ='text/javascript'; 
     script.defer = true; 
     document.getElementsByTagName('head').item(0).appendChild(script); 

     script.onload = function(){ 
     resolve() 
     } 
     script.onerror = function(){ 
      reject() 
     } 
     }) 

/*I HAVE MODIFIED THIS TO BE PROMISE-BASED 
    HOW TO USE THIS FUNCTION 

    include('js/somefile.js').then(function(){ 
    console.log('loaded'); 
    },function(){ 
    console.log('not loaded'); 
    }) 
    */ 
} 
Cuestiones relacionadas