document.createElement (“script”) in modo sincrono

È ansible chiamare un file .js sincrono e quindi utilizzarlo subito dopo?

  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(); };  

Questo è semplificato. Nella mia implementazione la roba createElement ha una funzione. Ho pensato di aggiungere qualcosa alla funzione che potrebbe verificare se una determinata variabile è stata istanziata prima di restituire il controllo. Ma poi c’è ancora il problema di cosa fare quando si include js da un altro sito su cui non ho alcun controllo.

Pensieri?

Modificare:

Ho accettato la migliore risposta per ora perché dà una buona spiegazione per quello che sta succedendo. Ma se qualcuno ha qualche suggerimento su come migliorarlo, sono aperto a loro. Ecco un esempio di cosa mi piacerebbe fare.

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

Voglio solo evitare di dover conoscere troppo gli interni e solo essere in grado di dire: “Desidero utilizzare questo modulo, e ora userò del codice da esso.”

Puoi creare il tuo elemento con un gestore "onload", che verrà chiamato quando lo script è stato caricato e valutato dal browser.

 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); 

Non puoi farlo in modo sincrono.

modifica - è stato sottolineato che, fedele alla forma, IE non triggers un evento "carico" sui tag caricati / valutati. Quindi suppongo che la prossima cosa da fare sia recuperare lo script con XMLHttpRequest e poi eval() tu stesso. (Oppure, suppongo, riempia il testo in un tag che aggiungi, l'ambiente di esecuzione di eval() è influenzato dall'ambito locale, quindi non farà necessariamente ciò che vuoi che faccia.)

modifica - A partire dall'inizio del 2013 , consiglierei vivamente di cercare uno strumento di caricamento degli script più robusto come Requirejs . Ci sono molti casi speciali di cui preoccuparsi. Per situazioni davvero semplici, c'è yepnope , che ora è incorporato in Modernizr .

Questo non è carino, ma funziona:

 ');   

O

 '); window.onload = function() { functionFromOther(); };  

Lo script deve essere incluso in un tag separato o prima di window.onload() .

Questo non funzionerà:

 '); functionFromOther(); // Error  

Lo stesso può essere fatto con la creazione di un nodo, come ha fatto Pointy, ma solo in FF. Non hai alcuna garanzia quando lo script sarà pronto in altri browser.

Essendo un purista XML, lo odio davvero. Ma funziona in modo prevedibile. Potresti facilmente avvolgere quei brutti document.write() s in modo da non doverli guardare. Potresti anche fare test e creare un nodo e aggiungerlo per poi ricadere su document.write() .

È molto tardi, ma per riferimento futuro a chiunque desideri farlo, puoi utilizzare quanto segue:

 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); } 

Ho fatto un breve post sul blog qualche tempo fa http://crlog.info/2011/10/06/dynamically-requireinclude-a-javascript-file-into-a-page-and-be-notified-when-its -caricato/

La programmazione asincrona è leggermente più complicata perché la conseguenza di effettuare una richiesta è incapsulata in una funzione invece di seguire l’istruzione della richiesta. Ma il comportamento in tempo reale che l’ utente sperimenta può essere significativamente migliore perché non vedrà un server lento o una rete lenta che causerà il browser come se si fosse arrestato. La programmazione sincrona è irrispettosa e non dovrebbe essere impiegata in applicazioni utilizzate dalle persone.

Douglas Crockford ( blog YUI )

Va bene, allaccia i sedili, perché sarà una corsa sconnessa. Sempre più persone chiedono di caricare gli script in modo dinamico tramite javascript, sembra essere un argomento scottante.

I motivi principali per cui questo è diventato così popolare sono:

  • modularità lato client
  • più facile gestione delle dipendenze
  • gestione degli errori
  • vantaggi prestazionali

Informazioni sulla modularità : è ovvio che la gestione delle dipendenze lato client dovrebbe essere gestita direttamente dal lato client. Se è necessario un determinato object, modulo o libreria, lo chiediamo e lo cariciamo in modo dinamico.

Gestione degli errori : se una risorsa fallisce, si ha ancora la possibilità di bloccare solo le parti che dipendono dallo script interessato, o magari addirittura dargli un’altra prova con un certo ritardo.

Le prestazioni sono diventate un vantaggio competitivo tra i siti Web, ora è un fattore di ranking della ricerca. Quello che gli script dinamici possono fare è imitare il comportamento asincrono rispetto al modo di blocco predefinito di come i browser gestiscono gli script. Gli script bloccano altre risorse, gli script bloccano l’ ulteriore analisi del documento HTML, gli script bloccano l’interfaccia utente. Ora con i tag script dinamici e le sue alternative cross-browser è ansible fare richieste asincrone reali ed eseguire codice dipendente solo quando sono disponibili. I tuoi script caricheranno in parallelo anche con altre risorse e il rendering sarà impeccabile.

Il motivo per cui alcune persone si attengono allo scripting sincrono è perché sono abituati. Pensano che sia il modo predefinito, è il modo più semplice, e alcuni potrebbero persino pensare che sia l’unico modo.

Ma l’unica cosa di cui dovremmo preoccuparci quando questo deve essere deciso in merito al design di un’applicazione è l’ esperienza dell’utente finale . E in questa zona asincrona non si può sconfiggere. L’utente riceve risposte immediate (o dice promesse) e una promise è sempre meglio di niente. Uno schermo vuoto spaventa le persone. Gli sviluppatori non dovrebbero essere pigri per migliorare le prestazioni percepite .

E infine alcune parole sul lato sporco. Cosa dovresti fare per farlo funzionare su tutti i browser:

  1. imparare a pensare in modo asincrono
  2. organizza il tuo codice per essere modulare
  3. organizza il tuo codice per gestire bene errori e casi limite
  4. migliorare progressivamente
  5. prenditi sempre cura della giusta quantità di feedback

Le risposte di cui sopra mi hanno indirizzato nella giusta direzione. Ecco una versione generica di ciò che ho lavorato:

  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 } 

Sembra una buona panoramica del caricamento dinamico degli script: http://unixpapa.com/js/dyna.html

Ho avuto il seguente problema (s) con le risposte esistenti a questa domanda (e le variazioni di questa domanda su altri thread StackOverflow):

  • Nessuno del codice caricato è stato debuggato
  • Molte delle soluzioni richiedevano i callback per sapere quando il caricamento era terminato invece di bloccare realmente, il che significa che avrei ricevuto errori di esecuzione dal chiamare immediatamente il codice caricato (cioè carico).

O, leggermente più accuratamente:

  • Nessuno del codice caricato è stato debuggato (tranne dal blocco di tag dello script HTML, se e solo se la soluzione ha aggiunto elementi di script a dom e mai come singoli script visualizzabili.) => Dato quanti script devo caricare ( e debug), questo era inaccettabile.
  • Le soluzioni che utilizzano gli eventi “onreadystatechange” o “onload” non sono riuscite a bloccare, il che rappresentava un grosso problema poiché il codice inizialmente caricava gli script dinamici in modo sincrono usando ‘require ([filename,’ dojo / domReady ‘]);’ e stavo strappando il dojo.

La mia soluzione finale, che carica lo script prima di tornare, e ha tutti gli script correttamente accessibili nel debugger (almeno per Chrome) è la seguente:

ATTENZIONE: il seguente codice dovrebbe essere PROBABILMENTE usato solo in modalità ‘sviluppo’. (Per la modalità ‘release’ raccomando il preconfezionamento e la minifrazione SENZA il caricamento dinamico degli script, o almeno senza 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); } }; 
 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'); }) */ } 

Sono abituato ad avere più file .js sul mio sito web che dipendono l’uno dall’altro. Per caricarli e assicurare che le dipendenze siano valutate nel giusto ordine, ho scritto una funzione che carica tutti i file e quindi, una volta ricevuti, li eval() . Lo svantaggio principale è che dal momento che questo non funziona con CDN. Per tali librerie (ad esempio, jQuery) è meglio includerle staticamente. Tieni presente che l’inserimento dinamico dei nodes di script nell’HTML non garantisce che gli script vengano valutati nell’ordine corretto, almeno non in Chrome (questo era il motivo principale per scrivere questa funzione).

 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); } } 

Non ho capito come creare riferimenti allo stesso valore senza creare un array (per contare). Altrimenti penso che sia autoesplicativo (quando tutto è caricato, eval() ogni file nell'ordine dato, altrimenti basta memorizzare la risposta).

Esempio di utilizzo:

 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' ]) ; 

Ironia della sorte, ho quello che vuoi, ma voglio qualcosa di più vicino a quello che hai avuto.

Sto caricando le cose in modo dinamico e asincrono, ma con un richiamo del load come tale (usando dojo e xmlhtpprequest)

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

Per una spiegazione più dettagliata, vedi qui

Il problema è che da qualche parte lungo la linea il codice viene valutato, e se c’è qualcosa di sbagliato nel tuo codice, il console.error(errorMessage); l’istruzione indicherà la riga in cui eval() è, non l’errore effettivo. Questo è un grosso problema che sto effettivamente cercando di riconvertire in dichiarazioni (vedi qui .