Injizieren Sie ein Skript-Tag mit remote src und warten Sie, bis es ausgeführt wird

70

Wie kann ich ein <script src="https://remote.com/"></script>Element in meine Seite einfügen, auf die Ausführung warten und dann die von ihm definierten Funktionen verwenden?

Zu Ihrer Information: In meinem Fall führt das Skript in seltenen Fällen eine Kreditkartenverarbeitung durch, daher möchte ich es nicht immer einschließen. Ich möchte es schnell einschließen, wenn der Benutzer ein Dialogfeld zum Ändern der Kreditkartenoptionen öffnet und ihm dann die neuen Kreditkartenoptionen sendet.

Für weitere Details bearbeiten: Ich habe keinen Zugriff auf das Remote-Skript.

Riley Lark
quelle
Haben Sie die Kontrolle über das Remote-Skript? Wenn ja, ist es möglicherweise einfacher, wenn das Skript selbst Ihren Code
aufruft,
Leider habe ich keine Kontrolle über das Remote-Skript. Bearbeitungsfrage, um das zu reflektieren ~
Riley Lark

Antworten:

139

Sie können Google Analytics oder die Methode von Facebook verwenden:

(function(d, script) {
    script = d.createElement('script');
    script.type = 'text/javascript';
    script.async = true;
    script.onload = function(){
        // remote script has loaded
    };
    script.src = 'http://www.google-analytics.com/ga.js';
    d.getElementsByTagName('head')[0].appendChild(script);
}(document));

AKTUALISIEREN:

Unten finden Sie die neue Facebook-Methode. Es basiert auf einem vorhandenen Skript-Tag anstelle von <head>:

(function(d, s, id){
    var js, fjs = d.getElementsByTagName(s)[0];
    if (d.getElementById(id)){ return; }
    js = d.createElement(s); js.id = id;
    js.onload = function(){
        // remote script has loaded
    };
    js.src = "//connect.facebook.net/en_US/sdk.js";
    fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
  • Ersetzen Sie diese facebook-jssdkdurch Ihre eindeutige Skript-ID, um zu vermeiden, dass sie mehrmals angehängt wird.
  • Ersetzen Sie die URL des Skripts durch Ihre eigene.
Pierre
quelle
Dies scheint perfekt zu sein, aber es gibt einige Gerüchte im Internet, dass Onload nicht von allen Browsern aufgerufen wird. Wissen Sie, mit welchen Browsern dies funktionieren würde?
Riley Lark
Warum hängst du das nicht an den Körper an?
Ben Taliadoros
1
Anstatt script.onloadoder zu verwenden js.onload, würde ich dem loadEreignis einen Ereignis-Listener hinzufügen . stackoverflow.com/questions/15564029/…
ha404
Gibt es einen Vorteil beim Anhängen an einen anderen scriptgegenüber dem Anhängen an den head? Der Nachteil, den ich beim Anhängen an scripts sehe, ist, wenn es keine zum Anhängen gibt, würde es brechen.
ha404
6
Beachten Sie, dass die erste Abfrage d.getElementsByTagName(s)[0]unter die ifPrüfung verschoben werden kann , um sich eine DOM-Abfrage zu sichern, wenn das Skript bereits vorhanden ist.
mAAdhaTTah
43

Gleiche Methode mit Ereignis-Listenern und ES2015-Konstrukten:

function injectScript(src) {
    return new Promise((resolve, reject) => {
        const script = document.createElement('script');
        script.src = src;
        script.addEventListener('load', resolve);
        script.addEventListener('error', e => reject(e.error));
        document.head.appendChild(script);
    });
}

injectScript('https://example.com/script.js')
    .then(() => {
        console.log('Script loaded!');
    }).catch(error => {
        console.error(error);
    });
Frank Gambino
quelle
Nett! Dies wäre ein gutes NPM-Paket oder ES6-Modul. +1 für eine Beispielverwendung.
A-Diddy
Haben Sie eine Idee, warum ich in then () body keine im geladenen Skript definierten Variablen / Funktionen verwenden kann?
Dar0x
11

Dies ist eine Möglichkeit, eine Liste von Skripten dynamisch zu laden und synchron auszuführen. Sie müssen jedes Skript-Tag in das DOM einfügen und sein asynchrones Attribut explizit auf false setzen:

script.async = false;

Skripte, die in das DOM eingefügt wurden, werden standardmäßig asynchron ausgeführt. Sie müssen daher das asynchrone Attribut manuell auf false setzen, um dies zu umgehen.

Beispiel

<script>
(function() {
  var scriptNames = [
    "https://code.jquery.com/jquery.min.js",
    "example.js"
  ];
  for (var i = 0; i < scriptNames.length; i++) {
    var script = document.createElement('script');
    script.src = scriptNames[i];
    script.async = false; // This is required for synchronous execution
    document.head.appendChild(script);
  }
  // jquery.min.js and example.js will be run in order and synchronously
})();
</script>

<!-- Gotcha: these two script tags may still be run before `jquery.min.js`
     and `example.js` -->
<script src="example2.js"></script>
<script>/* ... */<script>

Verweise

Flimm
quelle
Es funktioniert jetzt, positiv bewertet. Übrigens fehlt dem letzten Schlussskript der Backslash.
Washington Guedes
6

so etwas sollte den Trick machen:

(function() {
    // Create a new script node
    var script = document.createElement("script");
    script.type = "text/javascript";
    script.onload = function() {
        // Cleanup onload handler
        script.onload = null;

        // do stuff with the loaded script!

    }

    // Add the script to the DOM
    (document.getElementsByTagName( "head" )[ 0 ]).appendChild( script );

    // Set the `src` to begin transport
    script.src = "https://remote.com/";
})();

hoffentlich hilft das! Prost.

Keeganwatkins
quelle
Gibt es einen Grund, warum Sie das Skript-Onload im Rückruf manuell auf null setzen? Gibt es ein Problem damit, dass es mehr als einmal ausgelöst wird, oder erzwingt dies nur eine Art Speicherbereinigung?
James Tomasino
Ja, das heißt, um Speicherverluste im alten IE zu vermeiden, da das Skriptelement auf eine Funktion als Onload-Handler verweist.
Keeganwatkins
5

Dynamisch import()

Mit dem dynamischen Import können Sie jetzt Module laden und warten, bis sie ausgeführt werden. So einfach ist das:

import("http://example.com/module.js").then(function(module) {
  alert("module ready");
});

Wenn das Modul bereits geladen und ausgeführt wurde, wird es nicht erneut geladen und ausgeführt, aber das von zurückgegebene Versprechen importwird weiterhin aufgelöst.

Beachten Sie, dass die Datei als Modul und nicht nur als Skript geladen wird. Module werden im strengen Modus ausgeführt und im Modulbereich geladen. Dies bedeutet, dass Variablen nicht automatisch global gemacht werden, wie sie es in normal geladenen Skripten sind. Verwenden Sie das exportSchlüsselwort in einem Modul, um eine Variable für andere Module oder Skripte freizugeben.

Verweise:

Flimm
quelle
1

Erstellen Sie einen Lader

Sie können das Skript ordnungsgemäß in einen Loader einfügen.

Beachten Sie, dass die Ausführung der dynamisch geladenen Skripte normalerweise nach statisch geladenen Skripten (dh <script src="My_script.js"></script>) erfolgt (die Reihenfolge der Injektion im DOM garantiert nicht das Gegenteil):

zB loader.js:

function appendScript(url){
   let script = document.createElement("script");
   script.src = url;
   script.async = false //IMPORTANT
   /*Node Insertion Point*/.appendChild(script);
}
appendScript("my_script1.js");
appendScript("my_script2.js");

my_script1.jswird effektiv vorher ausgeführt my_script2.js(hilfreich, wenn Abhängigkeiten von vorhanden my_script2.jssind my_script1.js)

Beachten Sie, dass dies wichtig ist, script.async = falseda dynamisch geladene Skripte async = truestandardmäßig asyncdie Reihenfolge des Ladens nicht gewährleisten.

Sheed
quelle
0

Hier ist meine angepasste Version, basierend auf der Antwort von Frank:

static async importScript(src, expressionToEvaluateAndReturn){

        return new Promise((resolve, reject) => {
            const script = document.createElement('script');
            script.async = true;
            script.src = src;
            script.addEventListener('load', (event)=>{
                if(expressionToEvaluateAndReturn){
                    try{
                        let result = eval(expressionToEvaluateAndReturn);
                        resolve(result);
                    } catch(error){
                        reject(error);
                    }

                } else {
                    resolve();
                }
            });
            script.addEventListener('error', () => reject('Error loading script "' + src + '"'));
            script.addEventListener('abort', () => reject('Script loading aborted for "' + src + '"'));
            document.head.appendChild(script);
        });    

    }   

Anwendungsbeispiel:

let d3 = await importScript('/bower_components/d3/d3.min.js','d3')
                    .catch(error => {
                        console.log(error);
                        throw error;
                    });
Stefan
quelle