caricare gli script in modo asincrono

Sto usando diversi plugin, widget personalizzati e alcune altre librerie da JQuery. di conseguenza ho diversi file .js e .css. Devo creare un caricatore per il mio sito perché ci vuole un po ‘di tempo per caricarlo. sarà bello se riesco a visualizzare il loader prima di importare tutti i:

   ... .... etc 

Ho trovato diverse esercitazioni che mi consentono di importare una libreria JavaScript in modo asincrono. per esempio posso fare qualcosa come:

  (function () { var s = document.createElement('script'); s.type = 'text/javascript'; s.async = true; s.src = 'js/jquery-ui-1.8.16.custom.min.js'; var x = document.getElementsByTagName('script')[0]; x.parentNode.insertBefore(s, x); })(); 

per qualche ragione quando faccio la stessa cosa per tutti i miei file, le pagine non funzionano. Ho cercato per così tanto tempo di provare a trovare il problema, ma non riesco a trovarlo. Per prima cosa ho pensato che fosse probabilmente perché alcune funzioni javascript dipendevano dalle altre. ma li ho caricati nel giusto ordine utilizzando la funzione di timeout quando ho completato il procedimento successivo e la pagina si comporta ancora in modo strano. per esempio non sono in grado di fare clic sui collegamenti, ecc … le animazioni funzionano comunque.

In ogni modo

Ecco cosa ho pensato … Credo che i browser abbiano una cache, ecco perché ci vuole molto tempo per caricare la pagina per la prima volta e la prossima volta è veloce. quindi quello che sto pensando di fare è sostituire la mia pagina index.html con una pagina che carica tutti questi file in modo asincrono. quando ajax ha terminato il caricamento di tutti quei file redirect alla pagina che ho intenzione di utilizzare. quando si utilizza quella pagina non dovrebbe richiedere molto tempo per il caricamento in quanto i file dovrebbero essere inclusi nella cache del browser. sulla mia pagina indice (pagina in cui i file .js e .css vengono caricati in modo asincrono) Non mi interessa ricevere errori. Visualizzerò semplicemente un caricatore e reindirizzando la pagina una volta fatto …

Questa idea è una buona alternativa? o dovrei continuare a provare ad implementare i metodi asincroni?


MODIFICARE

il modo in cui carico tutto async è come:

 importScripts(); function importScripts() { //import: jquery-ui-1.8.16.custom.min.js getContent("js/jquery-1.6.2.min.js",function (code) { var s = document.createElement('script'); s.type = 'text/javascript'; //s.async = true; s.innerHTML=code; var x = document.getElementsByTagName('script')[0]; x.parentNode.insertBefore(s, x); setTimeout(insertNext1,1); }); //import: jquery-ui-1.8.16.custom.min.js function insertNext1() { getContent("js/jquery-ui-1.8.16.custom.min.js",function (code) { var s = document.createElement('script'); s.type = 'text/javascript'; s.innerHTML=code; var x = document.getElementsByTagName('script')[0]; x.parentNode.insertBefore(s, x); setTimeout(insertNext2,1); }); } //import: jquery-ui-1.8.16.custom.css function insertNext2() { getContent("css/custom-theme/jquery-ui-1.8.16.custom.css",function (code) { var s = document.createElement('link'); s.type = 'text/css'; s.rel ="stylesheet"; s.innerHTML=code; var x = document.getElementsByTagName('script')[0]; x.parentNode.insertBefore(s, x); setTimeout(insertNext3,1); }); } //import: main.css function insertNext3() { getContent("css/main.css",function (code) { var s = document.createElement('link'); s.type = 'text/css'; s.rel ="stylesheet"; s.innerHTML=code; var x = document.getElementsByTagName('script')[0]; x.parentNode.insertBefore(s, x); setTimeout(insertNext4,1); }); } //import: jquery.imgpreload.min.js function insertNext4() { getContent("js/farinspace/jquery.imgpreload.min.js",function (code) { var s = document.createElement('script'); s.type = 'text/javascript'; s.innerHTML=code; var x = document.getElementsByTagName('script')[0]; x.parentNode.insertBefore(s, x); setTimeout(insertNext5,1); }); } //import: marquee.js function insertNext5() { getContent("js/marquee.js",function (code) { var s = document.createElement('script'); s.type = 'text/javascript'; s.innerHTML=code; var x = document.getElementsByTagName('script')[0]; x.parentNode.insertBefore(s, x); setTimeout(insertNext6,1); }); } //import: marquee.css function insertNext6() { getContent("css/marquee.css",function (code) { var s = document.createElement('link'); s.type = 'text/css'; s.rel ="stylesheet"; s.innerHTML=code; var x = document.getElementsByTagName('script')[0]; x.parentNode.insertBefore(s, x); setTimeout(insertNext,1); }); } function insertNext() { setTimeout(pageReadyMan,10); } } // get the content of url and pass that content to specified function function getContent( url, callBackFunction ) { // attempt to create the XMLHttpRequest and make the request try { var asyncRequest; // variable to hold XMLHttpRequest object asyncRequest = new XMLHttpRequest(); // create request object // register event handler asyncRequest.onreadystatechange = function(){ stateChange(asyncRequest, callBackFunction); } asyncRequest.open( 'GET', url, true ); // prepare the request asyncRequest.send( null ); // send the request } // end try catch ( exception ) { alert( 'Request failed.' ); } // end catch } // end function getContent // call function whith content when ready function stateChange(asyncRequest, callBackFunction) { if ( asyncRequest.readyState == 4 && asyncRequest.status == 200 ) { callBackFunction(asyncRequest.responseText); } // end if } // end function stateChange 

e la parte strana è che tutto il lavoro dello stile e tutte le funzioni javascript. la pagina è congelata per qualche motivo però …

Un paio di soluzioni per il caricamento asincrono:

 //this function will work cross-browser for loading scripts asynchronously 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() { //console.log( this.readyState ); //uncomment this line to see which ready states are called. if ( !r && (!this.readyState || this.readyState == 'complete') ) { r = true; callback(); } }; t = document.getElementsByTagName('script')[0]; t.parentNode.insertBefore(s, t); } 

Se hai già jQuery nella pagina, usa semplicemente:

$.getScript(url, successCallback) *

Inoltre, è ansible che i tuoi script vengano caricati / eseguiti prima del caricamento del documento, il che significa che dovrai attendere il document.ready Già prima che gli eventi possano essere associati agli elementi.

Non è ansible dire in modo specifico quale sia il tuo problema senza vedere il codice.

La soluzione più semplice è quella di mantenere tutti gli script in linea nella parte inferiore della pagina, in questo modo non bloccano il caricamento del contenuto HTML durante l’esecuzione. Evita anche il problema di dover caricare in modo asincrono ogni script richiesto.

Se si dispone di un’interazione particolarmente elaborata che non è sempre utilizzata che richiede uno script più grande di qualche tipo, potrebbe essere utile evitare di caricare quel particolare script fino a quando non è necessario (caricamento lento).

* Gli script caricati con $.getScript probabilmente non verranno memorizzati nella cache


Per chiunque possa utilizzare funzionalità moderne come l’object Promise , la funzione loadScript è diventata significativamente più semplice:

 function loadScript(src) { return new Promise(function (resolve, reject) { var s; s = document.createElement('script'); s.src = src; s.onload = resolve; s.onerror = reject; document.head.appendChild(s); }); } 

Tieni presente che questa versione non accetta più un argomento di callback poiché la promise restituita gestirà la richiamata. Quello che in precedenza sarebbe stato loadScript(src, callback) ora sarebbe loadScript(src).then(callback) .

Questo ha il vantaggio di essere in grado di rilevare e gestire i fallimenti, ad esempio si potrebbe chiamare …

 loadScript(cdnSource) .catch(loadScript.bind(null, localSource)) .then(successCallback, failureCallback); 

… e gestirà le interruzioni CDN con garbo.

Il nuovo attributo ‘asincrono’ di HTML5 dovrebbe fare il trucco. ‘differire’ è supportato anche nella maggior parte dei browser se ti interessa di IE.

async: HTML

  

differire – L’HTML

  

Durante l’analisi del nuovo codice dell’unità pubblicitaria adsense ho notato l’attributo e una ricerca mi ha portato qui: http://davidwalsh.name/html5-async

Ho caricato gli script in modo asincrono (html 5 ha questa caratteristica) quando tutti gli script hanno terminato il caricamento ho reindirizzato la pagina a index2.html dove index2.html utilizza le stesse librerie. Poiché i browser hanno una cache quando la pagina reindirizza a index2.html, index2.html viene caricata in meno di un secondo perché ha tutto ciò che serve per caricare la pagina. Nella mia pagina index.html caricherò anche le immagini che ho intenzione di utilizzare in modo che il browser collochi quelle immagini sulla cache. quindi il mio index.html ha il seguente aspetto:

     Project Management      
Loading

un’altra cosa interessante è che posso inserire un caricatore nella pagina e quando la pagina è pronta a caricare il caricatore andrà via e in un opaco di millisecondi la nuova pagina sarà in esecuzione.

Esempio da google

  

Diverse note:

  • s.async = true non è molto corretto per il doctype HTML5, corretto è s.async = 'async' (in realtà l’uso di true è corretto , grazie ad amn che lo ha indicato nel commento subito sotto)
  • Utilizzare i timeout per controllare l’ordine non è molto buono e sicuro, e inoltre si aumenta il tempo di caricamento, a parità di tutti i timeout!

Poiché c’è un motivo recente per caricare i file in modo asincrono, ma in ordine, ti consiglio un modo un po ‘più guidato dal tuo esempio (rimuovi console.log per uso di produzione :)):

 (function() { var prot = ("https:"===document.location.protocol?"https://":"http://"); var scripts = [ "path/to/first.js", "path/to/second.js", "path/to/third.js" ]; function completed() { console.log('completed'); } // FIXME: remove logs function checkStateAndCall(path, callback) { var _success = false; return function() { if (!_success && (!this.readyState || (this.readyState == 'complete'))) { _success = true; console.log(path, 'is ready'); // FIXME: remove logs callback(); } }; } function asyncLoadScripts(files) { function loadNext() { // chain element if (!files.length) completed(); var path = files.shift(); var scriptElm = document.createElement('script'); scriptElm.type = 'text/javascript'; scriptElm.async = true; scriptElm.src = prot+path; scriptElm.onload = scriptElm.onreadystatechange = \ checkStateAndCall(path, loadNext); // load next file in chain when // this one will be ready var headElm = document.head || document.getElementsByTagName('head')[0]; headElm.appendChild(scriptElm); } loadNext(); // start a chain } asyncLoadScripts(scripts); })(); 

Vorrei completare la risposta di zzzzBov con un controllo per la presenza di callback e consentire il passaggio di argomenti:

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

Potresti trovare questo articolo wiki interessante: http://ajaxpatterns.org/On-Demand_Javascript

Spiega come e quando usare tale tecnica.

Uno dei motivi per cui i tuoi script potrebbero essere caricati così lentamente è che se avessi eseguito tutti gli script durante il caricamento della pagina, in questo modo:

 callMyFunctions(); 

invece di:

 $(window).load(function() { callMyFunctions(); }); 

Questo secondo bit di script attende che il browser abbia caricato completamente tutto il codice Javascript prima che inizi a eseguire uno qualsiasi dei tuoi script, facendo apparire all’utente che la pagina è stata caricata più velocemente.

Se stai cercando di migliorare l’esperienza dell’utente riducendo il tempo di caricamento, non sceglierei l’opzione “schermata di caricamento”. A mio avviso, sarebbe molto più fastidioso che caricare la pagina più lentamente.

Ti suggerirei di dare un’occhiata a Modernizr . È una piccola libreria leggera che puoi caricare in modo asincrono il tuo javascript con funzionalità che ti permettono di controllare se il file è caricato ed eseguire lo script nell’altro specificato.

Ecco un esempio di caricamento di jQuery:

 Modernizr.load([ { load: '//ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.js', complete: function () { if ( !window.jQuery ) { Modernizr.load('js/libs/jquery-1.6.1.min.js'); } } }, { // This will wait for the fallback to load and // execute if it needs to. load: 'needs-jQuery.js' } ]); 

Grazie a HTML5, puoi ora dichiarare gli script che vuoi caricare in modo asincrono aggiungendo “async” nel tag:

  

Nota: l’attributo async è solo per gli script esterni (e dovrebbe essere usato solo se l’attributo src è presente).

Nota: esistono diversi modi per eseguire uno script esterno:

  • Se async è presente: lo script viene eseguito in modo asincrono con il resto della pagina (lo script verrà eseguito mentre la pagina continua l’analisi)
  • Se async non è presente e il rinvio è presente: lo script viene eseguito quando la pagina ha terminato l’analisi
  • Se non sono presenti né asincrona né differita: lo script viene recuperato ed eseguito immediatamente, prima che il browser continui ad analizzare la pagina

Vedi questo: http://www.w3schools.com/tags/att_script_async.asp

Ecco una grande soluzione contemporanea per il caricamento di script asincrono anche se si rivolge solo allo script js con async false .

C’è un grande articolo scritto in http://www.html5rocks.comImmergiti nelle acque torbide del caricamento degli script .

Dopo aver considerato molte possibili soluzioni, l’autore ha concluso che l’aggiunta di script js alla fine dell’elemento body è il modo migliore per evitare il blocco del rendering della pagina da parte degli script js.

Nel frattempo, l’autore ha aggiunto un’altra buona soluzione alternativa per le persone che sono disperatamente in grado di caricare ed eseguire script in modo asincrono.

Considerando che hai quattro script chiamati script1.js, script2.js, script3.js, script4.js puoi farlo applicando async = false :

 [ 'script1.js', 'script2.js', 'script3.js', 'script4.js' ].forEach(function(src) { var script = document.createElement('script'); script.src = src; script.async = false; document.head.appendChild(script); }); 

Ora, Spec dice : Scarica insieme, esegui in ordine non appena tutto il download.

Firefox <3.6, Opera dice: Non ho idea di cosa sia questa cosa “asincrona”, ma succede solo che eseguo script aggiunti tramite JS nell’ordine in cui vengono aggiunti.

Safari 5.0 dice: Capisco “async”, ma non capisco impostandolo su “false” con JS. Eseguirò i tuoi script non appena atterreranno, in qualsiasi ordine.

IE <10 dice: non ho idea di “async”, ma c’è una soluzione alternativa con “onreadystatechange”.

Tutto il resto dice: sono tuo amico, lo faremo dal libro.

Ora, il codice completo con IE <10 soluzione:

 var scripts = [ 'script1.js', 'script2.js', 'script3.js', 'script4.js' ]; var src; var script; var pendingScripts = []; var firstScript = document.scripts[0]; // Watch scripts load in IE function stateChange() { // Execute as many scripts in order as we can var pendingScript; while (pendingScripts[0] && ( pendingScripts[0].readyState == 'loaded' || pendingScripts[0].readyState == 'complete' ) ) { pendingScript = pendingScripts.shift(); // avoid future loading events from this script (eg, if src changes) pendingScript.onreadystatechange = null; // can't just appendChild, old IE bug if element isn't closed firstScript.parentNode.insertBefore(pendingScript, firstScript); } } // loop through our script urls while (src = scripts.shift()) { if ('async' in firstScript) { // modern browsers script = document.createElement('script'); script.async = false; script.src = src; document.head.appendChild(script); } else if (firstScript.readyState) { // IE<10 // create a script and add it to our todo pile script = document.createElement('script'); pendingScripts.push(script); // listen for state changes script.onreadystatechange = stateChange; // must set src AFTER adding onreadystatechange listener // else we'll miss the loaded event for cached scripts script.src = src; } else { // fall back to defer document.write(' 

Bene, x.parentNode restituisce l’elemento HEAD, quindi stai inserendo lo script subito prima del tag head. Forse è questo il problema

Prova invece x.parentNode.appendChild() .

Dai un’occhiata a questo https://github.com/stephen-lazarionok/async-resource-loader . Ha un esempio che mostra come caricare JS, CSS e più file con un solo colpo.

Puoi usare LABJS o RequreJS

Caricatori di script come LABJS , RequireJS miglioreranno la velocità e la qualità del tuo codice.

Hai preso in considerazione l’utilizzo di Fetch Injection? Ho lanciato una libreria open source chiamata fetch-inject per gestire casi come questi. Ecco come potrebbe essere il tuo loader usando la lib:

 fetcInject([ 'js/jquery-1.6.2.min.js', 'js/marquee.js', 'css/marquee.css', 'css/custom-theme/jquery-ui-1.8.16.custom.css', 'css/main.css' ]).then(() => { 'js/jquery-ui-1.8.16.custom.min.js', 'js/farinspace/jquery.imgpreload.min.js' }) 

Per la retrocompatibilità sfrutta il rilevamento delle caratteristiche e il ricorso a XHR Injection o Script DOM Elements, o semplicemente allinea i tag nella pagina usando document.write .

Ecco la mia soluzione personalizzata per eliminare JavaScript per il blocco del rendering:

 // put all your JS files here, in correct order const libs = { "jquery": "https://code.jquery.com/jquery-2.1.4.min.js", "bxSlider": "https://cdnjs.cloudflare.com/ajax/libs/bxslider/4.2.5/jquery.bxslider.min.js", "angular": "https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0-beta.2/angular.min.js", "ngAnimate": "https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0-beta.2/angular-animate.min.js" } const loadedLibs = {} let counter = 0 const loadAsync = function(lib) { var http = new XMLHttpRequest() http.open("GET", libs[lib], true) http.onload = () => { loadedLibs[lib] = http.responseText if (++counter == Object.keys(libs).length) startScripts() } http.send() } const startScripts = function() { for (var lib in libs) eval(loadedLibs[lib]) console.log("allLoaded") } for (var lib in libs) loadAsync(lib) 

In breve, carica tutti gli script in modo asincrono e quindi li esegue di conseguenza.

Github repo : https://github.com/mudroljub/js-async-loader

Ho scritto un post per dare una mano, puoi leggere di più qui https://timber.io/snippets/asynchronously-load-a-script-in-the-browser-with-javascript/ , ma ho allegato la class di supporto qui sotto. Attenderà automaticamente che uno script venga caricato e restituisca un attributo di finestra specificato una volta eseguito.

 export default class ScriptLoader { constructor (options) { const { src, global, protocol = document.location.protocol } = options this.src = src this.global = global this.protocol = protocol this.isLoaded = false } loadScript () { return new Promise((resolve, reject) => { // Create script element and set attributes const script = document.createElement('script') script.type = 'text/javascript' script.async = true script.src = `${this.protocol}//${this.src}` // Append the script to the DOM const el = document.getElementsByTagName('script')[0] el.parentNode.insertBefore(script, el) // Resolve the promise once the script is loaded script.addEventListener('load', () => { this.isLoaded = true resolve(script) }) // Catch any errors while loading the script script.addEventListener('error', () => { reject(new Error(`${this.src} failed to load.`)) }) }) } load () { return new Promise(async (resolve, reject) => { if (!this.isLoaded) { try { await this.loadScript() resolve(window[this.global]) } catch (e) { reject(e) } } else { resolve(window[this.global]) } }) } } 

L’utilizzo è come questo:

 const loader = new Loader({ src: 'cdn.segment.com/analytics.js', global: 'Segment', }) // scriptToLoad will now be a reference to `window.Segment` const scriptToLoad = await loader.load() 

Ecco una piccola funzione ES6 se qualcuno vuole usarlo in React per esempio

 import {uniqueId} from 'lodash' // optional /** * @param {String} file The path of the file you want to load. * @param {Function} callback (optional) The function to call when the script loads. * @param {String} id (optional) The unique id of the file you want to load. */ export const loadAsyncScript = (file, callback, id) => { const d = document if (!id) { id = uniqueId('async_script') } // optional if (!d.getElementById(id)) { const tag = 'script' let newScript = d.createElement(tag) let firstScript = d.getElementsByTagName(tag)[0] newScript.id = id newScript.async = true newScript.src = file if (callback) { // IE support newScript.onreadystatechange = () => { if (newScript.readyState === 'loaded' || newScript.readyState === 'complete') { newScript.onreadystatechange = null callback(file) } } // Other (non-IE) browsers support newScript.onload = () => { callback(file) } } firstScript.parentNode.insertBefore(newScript, firstScript) } else { console.error(`The script with id ${id} is already loaded`) } } 

Vorrei suggerire di esaminare prima i file e vedere se questo ti dà un grande aumento di velocità. Se il tuo host è lento, potresti provare a mettere quel contenuto statico su un CDN.