document.createElement ("script") synchron


81

Ist es möglich, eine .jsDatei synchron aufzurufen und unmittelbar danach zu verwenden?

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

Dies wird vereinfacht. In meiner Implementierung befindet sich das createElement-Zeug in einer Funktion. Ich dachte darüber nach, der Funktion etwas hinzuzufügen, das überprüfen könnte, ob eine bestimmte Variable instanziiert wurde, bevor die Kontrolle zurückgegeben wird. Aber dann gibt es immer noch das Problem, was zu tun ist, wenn js von einer anderen Site eingefügt werden, über die ich keine Kontrolle habe.

Gedanken?

Bearbeiten:

Ich habe die beste Antwort für den Moment akzeptiert, weil sie eine gute Erklärung dafür gibt, was los ist. Aber wenn jemand Vorschläge hat, wie man dies verbessern kann, bin ich offen für sie. Hier ist ein Beispiel dafür, was ich tun möchte.

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

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

Ich möchte nur vermeiden, dass ich die Interna zu genau kennen muss, und einfach sagen können: "Ich möchte dieses Modul verwenden, und jetzt werde ich Code daraus verwenden."


Ich habe nicht herausgefunden, wie man Verweise auf denselben Wert erstellt, ohne ein Array zu erstellen (für die Anzahl). Ansonsten halte ich es für selbsterklärend (wenn alles geladen ist, eval()jede Datei in der angegebenen Reihenfolge, sonst nur die Antwort speichern).
Kang Rofingi

Antworten:


134

Sie können Ihr <script>Element mit einem "Onload" -Handler erstellen. Dieser wird aufgerufen, wenn das Skript vom Browser geladen und ausgewertet wurde.

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

Sie können es nicht synchron machen.

Bearbeiten - Es wurde darauf hingewiesen, dass der IE formgetreu kein "Lade" -Ereignis auf <script>Tags auslöst, die geladen / ausgewertet werden. Daher würde ich annehmen, dass das nächste, was zu tun ist, das Skript mit einer XMLHttpRequest und dann eval()selbst abzurufen . (Oder ich nehme an, Sie füllen den Text in ein <script>Tag, das Sie hinzufügen. Die Ausführungsumgebung von eval()wird vom lokalen Bereich beeinflusst, sodass nicht unbedingt das getan wird, was Sie möchten.)

Bearbeiten - Ab Anfang 2013 würde ich dringend empfehlen, ein robusteres Tool zum Laden von Skripten wie Requirejs in Betracht zu ziehen . Es gibt viele Sonderfälle, über die man sich Sorgen machen muss. Für wirklich einfache Situationen gibt es yepnope , das jetzt in Modernizr integriert ist .


3
Leider ist es nicht browserübergreifend.
Gblazex

69
Ja wirklich?? Wer löst beim Laden eines Skripts kein "Lade" -Ereignis aus? Warte - sag es mir nicht.
Pointy

1
@Pointy Ich habe dieses Problem mit XMLHttpRequest und dann gelöst eval(). Das Debuggen ist jedoch ein Albtraum, da die Fehlermeldung meldet, dass die Zeile eval()angezeigt wird, nicht der tatsächliche Fehler
puk

3
Aber wie macht requirejs das dann? Wie schließen sie viele Skripte ein und feuern sie in der richtigen Reihenfolge ab?
mmm

4
Natürlich ist document.write () genau das, wonach Sie suchen. Nicht schön, aber es funktioniert.
Jiri Vetyska

26

Das ist nicht schön, aber es funktioniert:

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

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

Oder

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

Das Skript muss entweder in einem separaten <script>Tag oder vorher enthalten sein window.onload().

Dies wird nicht funktionieren:

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

Dasselbe kann beim Erstellen eines Knotens wie bei Pointy durchgeführt werden, jedoch nur in FF. Sie können nicht garantieren, wann das Skript in anderen Browsern verfügbar ist.

Als XML-Purist hasse ich das wirklich. Aber es funktioniert vorhersehbar. Sie könnten diese hässlichen document.write()s leicht einwickeln, damit Sie sie nicht ansehen müssen. Sie können sogar Tests durchführen, einen Knoten erstellen und anhängen und dann darauf zurückgreifen document.write().


Sind Sie sicher, dass Ihr erstes Code-Snippet in allen Browsern funktioniert?
Bogdan Gusiev

@ BogdanGusiev Ich bin nicht 100% sicher. Ich habe in IE 8 (den damals aktuellen Versionen von) Firefox und Chrome getestet. Möglicherweise funktioniert dies nicht mit XHTML-Doctypes, die als Inhaltstyp bereitgestellt werden application/xhtml+xml.
Josh Johnson

1
Leider können Skript-Tags nicht in JS-Dateien verwendet werden.
Clem

@Clem Du könntest a document.write("<SCR" + "IPT>" + "...").
John Weisz

Dies ist eine OK-Alternative für Skripte, in <head>die mehrere andere Abhängigkeiten (oder private Dateien) geladen werden.
Alecov

18

Dies ist viel zu spät, aber für zukünftige Hinweise auf alle, die dies tun möchten, können Sie Folgendes verwenden:

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

Ich habe vor einiger Zeit einen kurzen Blog-Beitrag dazu verfasst: http://crlog.info/2011/10/06/dynamically-requireinclude-a-javascript-file-into-a-page-and-be-notified-when-its -geladen /


funktioniert das wirklich siehe meine Frage: stackoverflow.com/questions/17978255/…
mmm

1
Das sieht interessant aus. Eine Frage ... warum muss die Rückrufmethode zweimal ausgeführt werden? (script.onload = Rückruf und Rückruf () in onreadystatechange verwendet)
Clem

1
onreadysteatechange ist für IE und wird nur auf IE ausgelöst, da die Onload für IE nicht ausgelöst wird
Guilherme Ferreira

7

Die asynchrone Programmierung ist etwas komplizierter, da die Konsequenz einer Anforderung in einer Funktion gekapselt ist, anstatt der Anforderungsanweisung zu folgen. Aber das Echtzeitverhalten , dass die Benutzererfahrung sein kann deutlich besser , weil sie nicht einen trägen Server oder träge Netzwerk Ursache des Browser zu handeln , als ob es abgestürzt war. Die synchrone Programmierung ist respektlos und sollte nicht in Anwendungen eingesetzt werden, die von Personen verwendet werden.

Douglas Crockford ( YUI-Blog )

Okay, schnall deine Sitze an, denn es wird eine holprige Fahrt. Immer mehr Leute fragen nach dem dynamischen Laden von Skripten über Javascript, es scheint ein heißes Thema zu sein.

Die Hauptgründe, warum dies so populär wurde, sind:

  • clientseitige Modularität
  • Einfacheres Abhängigkeitsmanagement
  • Fehlerbehandlung
  • Leistungsvorteile

Informationen zur Modularität : Es liegt auf der Hand, dass die Verwaltung clientseitiger Abhängigkeiten direkt clientseitig erfolgen sollte. Wenn ein bestimmtes Objekt, Modul oder eine Bibliothek benötigt wird, fragen wir einfach danach und laden es dynamisch.

Fehlerbehandlung : Wenn eine Ressource ausfällt, haben wir immer noch die Möglichkeit, nur die Teile zu blockieren, die vom betroffenen Skript abhängen, oder es sogar mit einer gewissen Verzögerung erneut zu versuchen.

Die Leistung ist zu einem Wettbewerbsvorteil zwischen Websites geworden. Sie ist jetzt ein Suchrankingfaktor. Dynamische Skripte können asynchrones Verhalten imitieren, im Gegensatz zu der Standardblockierungsmethode für den Umgang von Browsern mit Skripten. Skripte blockieren andere Ressourcen, Skripte blockieren das weitere Parsen des HTML-Dokuments, Skripte blockieren die Benutzeroberfläche. Mit dynamischen Skript-Tags und ihren browserübergreifenden Alternativen können Sie jetzt echte asynchrone Anforderungen ausführen und abhängigen Code nur ausführen, wenn sie verfügbar sind. Ihre Skripte werden auch mit anderen Ressourcen parallel geladen und das Rendering ist fehlerfrei.

Der Grund, warum manche Leute sich an synchrones Scripting halten, ist, dass sie daran gewöhnt sind. Sie denken, es ist der Standardweg, es ist der einfachere Weg, und einige denken vielleicht sogar, dass es der einzige Weg ist.

Das einzige, was uns wichtig ist, wenn dies in Bezug auf das Design einer Anwendung entschieden werden muss, ist die Endbenutzererfahrung . Und in diesem Bereich ist asynchron nicht zu schlagen. Der Benutzer erhält sofort Antworten (oder sagt Versprechen), und ein Versprechen ist immer besser als nichts. Ein leerer Bildschirm macht den Menschen Angst. Entwickler sollten nicht faul sein, die wahrgenommene Leistung zu verbessern .

Und zum Schluss noch ein paar Worte zur schmutzigen Seite. Was Sie tun sollten, damit es in allen Browsern funktioniert:

  1. lerne asynchron zu denken
  2. Organisieren Sie Ihren Code modular
  3. Organisieren Sie Ihren Code, um Fehler und Randfälle gut zu behandeln
  4. schrittweise verbessern
  5. Achten Sie immer auf die richtige Menge an Feedback

Danke, Galam. Ich denke, ich hätte klarer sein sollen. Ich hatte erwartet, dass dies am Ende asynchron sein würde. Ich möchte nur eine Möglichkeit, darauf zuzugreifen, die für den Programmierer logisch sinnvoll war. Ich wollte Dinge vermeiden wie: Import ("package.mod1", function () {// mache Sachen mit mod1}); Import ("package.mod2", function () {// mache Sachen mit mod2}); Ich habe mir Ihr Skript und Ihre Labs angesehen und bin zwar nett, aber für meine Bedürfnisse komplexer. Ich dachte, es könnte einen einfacheren Weg geben und wollte vermeiden, zusätzliche Abhängigkeiten einzuführen.
Josh Johnson

1
Sie haben den Punkt meines Beitrags verpasst. Es geht nur um die Benutzer. Dies sollte Ihre erste Priorität sein. Alles andere ist zweitrangig.
Gblazex

2
Galam, sehr guter Punkt. Benutzererfahrung ist sehr wichtig. Um es klar auszudrücken, ich bin nicht bereit, auf Benutzererfahrung ODER qualitativ hochwertigen, wartbaren Code zu verzichten. Ich werde in Closure und Labjs nachsehen, was sie für mich tun können. Aber vorerst muss ich mich vielleicht an <script> -Tags halten. Leider arbeite ich nicht alleine daran. Ich arbeite mit einem mittelgroßen Entwicklerteam zusammen, daher hat wartbarer Code hohe Priorität. Wenn nicht jeder herausfinden kann, wie die Bibliothek effizient genutzt werden kann, geht user exp direkt aus dem Fenster. Rückrufe sind intuitiv. Ein Rückruf, weil Sie ein Paket importiert haben, ist dies nicht.
Josh Johnson

Aus Gründen der Klarheit war "synchron" eine schlechte Wortwahl, um meinen Standpunkt zu verdeutlichen. Ich möchte nicht, dass der Browser beim Laden einfriert.
Josh Johnson

1
Was ist, wenn Sie synchron laden müssen? Wenn Sie tatsächlich blockieren müssen, um die Benutzererfahrung zu erhalten. Wenn Sie ein JavaScript-basiertes A / B- oder MVT-Testsystem verwenden. Wie möchten Sie den Inhalt asynchron laden und den Standard ersetzen, ohne einen Flickereffekt zu erhalten, der die Benutzererfahrung beeinträchtigt? Ich bin offen für Vorschläge. Ich habe über 500 Kollegen, die eine Lösung dafür wissen möchten. Wenn Sie keinen haben, kommen Sie bitte nicht mit Ausdrücken wie "Synchrone Programmierung ist respektlos und sollte nicht in Anwendungen verwendet werden, die von Personen verwendet werden.".
Transilvlad

6

Die obigen Antworten haben mich in die richtige Richtung gelenkt. Hier ist eine generische Version von dem, was ich zum Laufen gebracht habe:

  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
  }      

Wann wird postLoadFunction()gerufen?
Josh Johnson

1
@JoshJohnson script.addEventListener('load', postLoadFunction);bedeutet, dass postLoadFunction beim Laden des Skripts aufgerufen wird.
Eric

4

Ich hatte die folgenden Probleme mit den vorhandenen Antworten auf diese Frage (und Variationen dieser Frage in anderen Stackoverflow-Threads):

  • Keiner der geladenen Codes war debuggbar
  • Viele der Lösungen erforderten Rückrufe, um zu wissen, wann das Laden beendet war, anstatt wirklich zu blockieren, was bedeutet, dass ich Ausführungsfehler erhalten würde, wenn ich sofort geladenen (dh geladenen) Code aufrufe.

Oder etwas genauer:

  • Keiner der geladenen Codes war debuggbar (außer aus dem HTML-Skript-Tag-Block, wenn und nur wenn die Lösung dem dom Skriptelemente hinzufügte, und niemals als einzelne sichtbare Skripte.) => Angesichts der Anzahl der zu ladenden Skripte ( und debug), das war inakzeptabel.
  • Lösungen, die Ereignisse 'onreadystatechange' oder 'onload' verwenden, konnten nicht blockiert werden. Dies war ein großes Problem, da der Code ursprünglich dynamische Skripts synchron mit 'require ([Dateiname,' dojo / domReady ']) geladen hat.' und ich zog Dojo aus.

Meine endgültige Lösung, bei der das Skript vor der Rückkehr geladen wird und auf die alle Skripte im Debugger ordnungsgemäß zugreifen (zumindest für Chrome), lautet wie folgt:

WARNUNG: Der folgende Code sollte wahrscheinlich nur im Entwicklungsmodus verwendet werden. (Für den 'Release'-Modus empfehle ich das Vorverpacken und Minimieren OHNE dynamisches Laden von Skripten oder zumindest ohne Auswertung).

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

4
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');
  })
  */
}


1

Ich bin es gewohnt, mehrere .js-Dateien auf meiner Website zu haben, die voneinander abhängen. Um sie zu laden und sicherzustellen, dass die Abhängigkeiten in der richtigen Reihenfolge ausgewertet werden, habe ich eine Funktion geschrieben, die alle Dateien und dann, sobald sie alle empfangen wurden, lädt eval(). Der Hauptnachteil ist, dass dies mit CDN nicht funktioniert. Für solche Bibliotheken (z. B. jQuery) ist es besser, sie statisch einzuschließen. Beachten Sie, dass das dynamische Einfügen von Skriptknoten in HTML nicht garantiert, dass Skripte in der richtigen Reihenfolge ausgewertet werden, zumindest nicht in Chrome (dies war der Hauptgrund für das Schreiben dieser Funktion).

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

Ich habe nicht herausgefunden, wie man Verweise auf denselben Wert erstellt, ohne ein Array zu erstellen (für die Anzahl). Ansonsten halte ich es für selbsterklärend (wenn alles geladen ist, eval()jede Datei in der angegebenen Reihenfolge, sonst einfach die Antwort speichern).

Anwendungsbeispiel:

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

0

Ironischerweise habe ich, was Sie wollen, aber etwas näher an dem, was Sie hatten.

Ich loadlade Dinge dynamisch und asynchron ein, aber mit einem solchen Rückruf (mit dojo und xmlhtpprequest)

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

Eine ausführlichere Erklärung finden Sie unter hier

Das Problem ist, dass der Code irgendwo entlang der Zeile ausgewertet wird. Wenn etwas mit Ihrem Code nicht stimmt, gibt die console.error(errorMessage);Anweisung die Zeile an eval(), in der sich der Code befindet, und nicht den tatsächlichen Fehler. Dies ist so ein großes Problem, dass ich tatsächlich versuche, es wieder in <script>Anweisungen umzuwandeln (siehe hier) .


Unterhaltsame Tatsache: Auch ich bin zu <script>Tags zurückgekehrt und habe Konventionen (zusammen mit einigen Build-Paketen) verwendet, um meine Js auf eine sinnvolle Weise zu verpacken.
Josh Johnson

@JoshJohnson Ich bin nicht so glücklich b / c Ich muss eine breite erste Ladung von Paketen mit Skripten innerhalb von Ringen tun, die asynchron geladen werden und Skripten zwischen Ringen werden synchron geladen
puk

Ich hatte Glück und konnte etwas herausfinden. Ich beneide dich nicht um deine Position.
Josh Johnson

0

Dies funktioniert für moderne "immergrüne" Browser, die Async / Warten und Abrufen unterstützen .

Dieses Beispiel wird ohne Fehlerbehandlung vereinfacht, um die grundlegenden Prinzipien bei der Arbeit zu zeigen.

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

Wir können es nicht so sagen wie webpack... 1. Für jedes Skript wird ein gesendet new HTTP request, 2. Dies überprüft auch nicht die Abhängigkeiten zwischen ihnen, 3. Nicht alle Browser unterstützen async/awaitund 4. In Bezug auf die Leistung sind wir mühsam als normal. Es wäre gut, dies inhead
Santosh
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.