¿Es posible llamar a un .js
archivo sincrónicamente y luego usarlo inmediatamente después?
<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 está simplificado. En mi implementación, el elemento createElement está en una función. Pensé en agregar algo a la función que pudiera verificar si una determinada variable fue instanciada antes de devolver el control. Pero aún queda 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 ahora porque da una buena explicación de lo que está sucediendo. Pero si alguien tiene alguna sugerencia sobre cómo mejorar esto, estoy abierto a ellos. He aquí 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');
Solo quiero evitar tener que conocer demasiado los aspectos internos y poder decir: "Deseo usar este módulo, y ahora usaré algún código de él".
fuente
eval()
cada archivo en el orden dado, de lo contrario, simplemente almacene la respuesta).Respuestas:
Puede crear su
<script>
elemento con un controlador "onload", que será llamado cuando el navegador haya cargado y evaluado el script.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 puedes hacerlo sincrónicamente.
editar - se ha señalado que, fiel a la forma, IE no dispara un evento de "carga" en las
<script>
etiquetas que se cargan / evalúan. Por lo tanto, supongo que lo siguiente que debe hacer sería buscar el script con un XMLHttpRequest y luegoeval()
usted mismo. (O, supongo, introduzca el texto en una<script>
etiqueta que agregue; el entorno de ejecución deeval()
se ve afectado por el alcance local, por lo que no necesariamente hará lo que usted desea que haga).editar : a principios de 2013 , recomiendo encarecidamente buscar una herramienta de carga de scripts más sólida como Requirejs . Hay muchos casos especiales de los que preocuparse. Para situaciones realmente simples, está yepnope , que ahora está integrado en Modernizr .
fuente
eval()
. Sin embargo, depurarlo es una pesadilla porque el mensaje de error informa queeval()
aparece la línea , no el error realEsto 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 incluirse en una
<script>
etiqueta separada o anteswindow.onload()
.Esto no funcionará:
<script type="text/javascript"> document.write('<script type="text/javascript" src="other.js"></script>'); functionFromOther(); // Error </script>
Se puede hacer lo mismo con la creación de un nodo, como hizo Pointy, pero solo en FF. No tiene ninguna garantía de cuándo estará listo el script en otros navegadores.
Siendo un purista de XML realmente odio esto. Pero funciona de manera predecible. Podrías envolver fácilmente esos feos
document.write()
para no tener que mirarlos. Incluso podría hacer pruebas y crear un nodo y agregarlo y luego recurrir a éldocument.write()
.fuente
application/xhtml+xml
.document.write("<SCR" + "IPT>" + "...")
.<head>
cuales cargan varias otras dependencias (o archivos privados).Esto es muy tarde, pero para referencia futura a cualquier persona que quiera hacer esto, puede usar lo siguiente:
function require(file,callback){ var head=document.getElementsByTagName("head")[0]; var script=document.createElement('script'); script.src=file; script.type='text/javascript'; //real browsers script.onload=callback; //Internet explorer script.onreadystatechange = function() { if (this.readyState == 'complete') { callback(); } } head.appendChild(script); }
Hice una breve publicación en el blog hace algún tiempo http://crlog.info/2011/10/06/dynamically-requireinclude-a-javascript-file-into-a-page-and-be-notified-when-its -cargado/
fuente
Douglas Crockford ( Blog de YUI )
Muy bien, abroche sus asientos, porque va a ser un viaje lleno de baches. Cada vez más personas preguntan sobre la carga de scripts dinámicamente a través de javascript, parece ser un tema candente.
Las principales razones por las que esto se hizo tan popular son:
Acerca de la modularidad : es obvio que la gestión de las dependencias del lado del cliente debe manejarse directamente en el lado del cliente. Si se necesita cierto objeto, módulo o biblioteca, simplemente lo solicitamos y lo cargamos dinámicamente.
Manejo de errores : si un recurso falla, todavía tenemos la oportunidad de bloquear solo las partes que dependen del script afectado, o incluso intentarlo de nuevo con cierta demora.
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 pueden hacer los scripts dinámicos es imitar el comportamiento asincrónico en contraposición a la forma de bloqueo predeterminada de cómo los navegadores manejan los scripts. Los scripts bloquean otros recursos, los scripts bloquean el análisis posterior del documento HTML, los scripts bloquean la interfaz de usuario. Ahora, con las etiquetas de secuencia de comandos dinámicas 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 el renderizado será impecable.
La razón por la que algunas personas se apegan a las secuencias de comandos sincrónicas es porque están acostumbradas. Creen que es la forma predeterminada, es la forma más fácil y algunos incluso pueden pensar que es la única forma.
Pero lo único que debería preocuparnos cuando se deba decidir sobre el diseño de una aplicación es la experiencia del usuario final . Y en esta área lo asincrónico no tiene rival. El usuario obtiene respuestas inmediatas (o dice promesas), y una promesa siempre es mejor que nada. Una pantalla en blanco asusta a la gente. Los desarrolladores no deberían ser perezosos para mejorar el rendimiento percibido .
Y finalmente algunas palabras sobre el lado sucio. Qué debe hacer para que funcione en todos los navegadores:
fuente
Las respuestas anteriores me señalaron en la dirección correcta. Aquí hay una versión genérica de lo que hice funcionar:
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 }
fuente
postLoadFunction()
llama?script.addEventListener('load', postLoadFunction);
significa que se llama a postLoadFunction al cargar el script.Tuve los siguientes problemas con las respuestas existentes a esta pregunta (y variaciones de esta pregunta en otros subprocesos de stackoverflow):
O, un poco más exactamente:
Mi solución final, que carga el script antes de regresar, Y tiene todos los scripts correctamente accesibles en el depurador (al menos para Chrome) es la siguiente:
ADVERTENCIA: El siguiente código PROBABLEMENTE debería usarse solo en modo 'desarrollo'. (Para el modo 'lanzamiento', recomiendo el preempaquetado y la minificación SIN carga dinámica de scripts, o al menos sin evaluación).
//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); } };
fuente
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'); }) */ }
fuente
Esto parece una descripción general decente de la carga dinámica de scripts: http://unixpapa.com/js/dyna.html
fuente
Estoy acostumbrado a tener varios archivos .js en mi sitio web que dependen unos de otros. Para cargarlos y asegurarme de que las dependencias se evalúen en el orden correcto, he escrito una función que carga todos los archivos y luego, una vez que se reciben todos,
eval()
ellos. El principal inconveniente es que, dado que esto no funciona con CDN. Para tales bibliotecas (por ejemplo, jQuery) es mejor incluirlas estáticamente. Tenga en cuenta que insertar nodos de secuencia de comandos en el HTML de forma dinámica no garantizará que las secuencias de comandos se evalúen en el orden correcto, al menos no en Chrome (esta fue la razón principal para escribir esta función).function xhrs(reqs) { var requests = [] , count = [] , callback ; callback = function (r,c,i) { return function () { if ( this.readyState == 4 ) { if (this.status != 200 ) { r[i]['resp']="" ; } else { r[i]['resp']= this.responseText ; } c[0] = c[0] - 1 ; if ( c[0] == 0 ) { for ( var j = 0 ; j < r.length ; j++ ) { eval(r[j]['resp']) ; } } } } } ; if ( Object.prototype.toString.call( reqs ) === '[object Array]' ) { requests.length = reqs.length ; } else { requests.length = 1 ; reqs = [].concat(reqs); } count[0] = requests.length ; for ( var i = 0 ; i < requests.length ; i++ ) { requests[i] = {} ; requests[i]['xhr'] = new XMLHttpRequest () ; requests[i]['xhr'].open('GET', reqs[i]) ; requests[i]['xhr'].onreadystatechange = callback(requests,count,i) ; requests[i]['xhr'].send(null); } }
No he descubierto cómo hacer referencias al mismo valor sin crear una matriz (para contar). De lo contrario, creo que se explica por sí mismo (cuando todo está cargado,
eval()
cada archivo en el orden indicado, de lo contrario, simplemente almacene la respuesta).Ejemplo de uso:
xhrs( [ root + '/global.js' , window.location.href + 'config.js' , root + '/js/lib/details.polyfill.min.js', root + '/js/scripts/address.js' , root + '/js/scripts/tableofcontents.js' ]) ;
fuente
Irónicamente, tengo lo que quieres, pero quiero algo más cercano a lo que tenías.
Estoy cargando cosas de forma dinámica y asincrónica, pero con una
load
devolución de llamada como esa (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 obtener una explicación más detallada, consulte aquí.
El problema es que en algún lugar de la línea se evalúa el código, y si hay algún problema con el código, la
console.error(errorMessage);
declaración indicará la línea dondeeval()
está, no el error real. Este es un problema TAN grande que en realidad estoy tratando de convertir nuevamente en<script>
declaraciones (ver aquí .fuente
<script>
etiquetas y he usado la convención (junto con algunos paquetes de compilación) para empaquetar mis js de una manera que tenga sentido.Esto funciona para los navegadores modernos 'perennes' que admiten async / await y fetch .
Este ejemplo está simplificado, sin manejo de errores, para mostrar los principios básicos en funcionamiento.
// This is a modern JS dependency fetcher - a "webpack" for the browser const addDependentScripts = async function( scriptsToAdd ) { // Create an empty script element const s=document.createElement('script') // Fetch each script in turn, waiting until the source has arrived // before continuing to fetch the next. for ( var i = 0; i < scriptsToAdd.length; i++ ) { let r = await fetch( scriptsToAdd[i] ) // Here we append the incoming javascript text to our script element. s.text += await r.text() } // Finally, add our new script element to the page. It's // during this operation that the new bundle of JS code 'goes live'. document.querySelector('body').appendChild(s) } // call our browser "webpack" bundler addDependentScripts( [ 'https://code.jquery.com/jquery-3.5.1.slim.min.js', 'https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js' ] )
fuente
webpack
... 1. para cada script, está enviando unnew HTTP request
, 2. Esto tampoco comprobará las dependencias entre ellos, 3. No todos los navegadores son compatiblesasync/await
y 4. En cuanto al rendimiento, es tedioso y luego normal. Sería bueno agregar esto enhead