简体   繁体   中英

How to wait for dynamically loaded scripts to fully execute in javascript?

In javascript, I load several scripts, and then want to run a function after. The issue is, my promise runs too early. It runs after each file is downloaded, but I need it to wait until they are all added to DOM, and fully executed.

(function() {

    function getFile(path) {
        return $.get(path, function (data) {
            $("body").append(data);
        });
    }

    $.when.apply($, [
        // load all the individual components
        "components/once/ti-snackbar/ti-snackbar.html",
        "components/once/ti-not-supported/ti-not-supported.html",
        "components/once/ti-drawer/ti-drawer.html",
        "components/widgets/ti-company-table-row/ti-company-table-row.html",
        "components/widgets/ti-chip-set/ti-chip-set.html",
        "components/widgets/ti-list/ti-list.html",
        "components/widgets/ti-user-card/ti-user-card.html",
        "components/widgets/ti-tabs/ti-tabs.html",
        "components/widgets/ti-data-table/ti-data-table.html",
        "components/forms/ti-new-company-form/ti-new-company-form.html",
        "components/forms/ti-edit-company-form/ti-edit-company-form.html",
        "components/pages/ti-page-inquire/ti-page-inquire.html",
        "components/pages/ti-page-companies/ti-page-companies.html",
        "components/pages/ti-page-report/ti-page-report.html",
        "components/pages/ti-page-admins/ti-page-admins.html",
        "components/pages/ti-page-contacts/ti-page-contacts.html",
        "components/pages/ti-page-policy/ti-page-policy.html",
        "components/pages/ti-page-manual/ti-page-manual.html",
        "components/pages/ti-page-404/ti-page-404.html",
        "components/tabs/ti-tab-name/ti-tab-name.html",
        "components/tabs/ti-tab-tags/ti-tab-tags.html",
        "components/tabs/ti-tab-status/ti-tab-status.html",   
        "components/tabs/ti-tab-restriction/ti-tab-restriction.html",     
        "components/tabs/ti-tab-other/ti-tab-other.html"   
    ].map(getFile)).then(function() {
        // render the full app after all components load
        getFile("components/once/ti-app/ti-app.html");
    });
})();

How can I fix it?

Note: each html file has a <script> and <template> .

Thanks

I see several possible issues with this code:

  1. You aren't forcing your $.get() calls to finish in order so you can get a variable completion order and things may get appended to the body in varying order (if that matters).

  2. You are using a mix of promises and callbacks which can make it difficult to accurately sequence things. In general, don't mix promises and callbacks. If you have promises in one place, use them everywhere (convert callbacks to promises if necessary).

  3. You say you "want to run a function after", but you don't explain where you're trying to put that function. To wait for all the promises, it will have to be in a .then() handler.

Here's an implementation that cleans these up:

(function() {

    function getFile(path) {
        return $.get(path);
    }

    function append(data) {
        $("body").append(data);
    }

    var resources = [
        "components/once/ti-snackbar/ti-snackbar.html",
        "components/once/ti-not-supported/ti-not-supported.html",
        "components/once/ti-drawer/ti-drawer.html",
        "components/widgets/ti-company-table-row/ti-company-table-row.html",
        "components/widgets/ti-chip-set/ti-chip-set.html",
        "components/widgets/ti-list/ti-list.html",
        "components/widgets/ti-user-card/ti-user-card.html",
        "components/widgets/ti-tabs/ti-tabs.html",
        "components/widgets/ti-data-table/ti-data-table.html",
        "components/forms/ti-new-company-form/ti-new-company-form.html",
        "components/forms/ti-edit-company-form/ti-edit-company-form.html",
        "components/pages/ti-page-inquire/ti-page-inquire.html",
        "components/pages/ti-page-companies/ti-page-companies.html",
        "components/pages/ti-page-report/ti-page-report.html",
        "components/pages/ti-page-admins/ti-page-admins.html",
        "components/pages/ti-page-contacts/ti-page-contacts.html",
        "components/pages/ti-page-policy/ti-page-policy.html",
        "components/pages/ti-page-manual/ti-page-manual.html",
        "components/pages/ti-page-404/ti-page-404.html",
        "components/tabs/ti-tab-name/ti-tab-name.html",
        "components/tabs/ti-tab-tags/ti-tab-tags.html",
        "components/tabs/ti-tab-status/ti-tab-status.html",   
        "components/tabs/ti-tab-restriction/ti-tab-restriction.html",     
        "components/tabs/ti-tab-other/ti-tab-other.html"   
    ];

    return Promise.all(resources.map(getFile)).then(function(data) {
        // append all items to the body in order now that they are all retrieved
        data.forEach(append);
    }).then(function() {
        // now that everything else is in place, load and append 
        // the part that uses those scripts and templates
        return getFile("components/once/ti-app/ti-app.html").then(append);
    }).catch(function(err) {
        // handle error here
        console.log(err);
        throw err;    // propagate error
    });
})().then(function() {
    // everything loaded here for code outside the IIFE here to know when it's all done
}).catch(function(err) {
    // error occurred here for code outside the IIFE here to know there was an error
});

The last .then().catch() is optional, if you want code outside the IIFE to be able to know when things are done or had an error.

Things modified and reasoning:

  1. All resources are loaded first and then appended in a predictable order which allows you to have interdependencies or any script initialization that can rely on previous dependencies. Before, there was no certain order that your scripts would be appended.

  2. ti-app.html won't be loaded and appended until all the other scripts are loaded and appended

  3. Moved the declaration of the array of scripts outside the main code flow just to improve the readability of the code flow.

  4. Added error detection

  5. Provide two places you can know when everything is done loading or if there was an error, one inside the IIFE and one outside the IIFE (depending upon what you need).

This could be useful: https://css-tricks.com/snippets/javascript/async-script-loader-with-callback/

var Loader = function () { }
Loader.prototype = {
    require: function (scripts, callback) {
        this.loadCount      = 0;
        this.totalRequired  = scripts.length;
        this.callback       = callback;

    for (var i = 0; i < scripts.length; i++) {
        this.writeScript(scripts[i]);
    }
},
loaded: function (evt) {
    this.loadCount++;

    if (this.loadCount == this.totalRequired && typeof this.callback == 'function') this.callback.call();
},
writeScript: function (src) {
    var self = this;
    var s = document.createElement('script');
    s.type = "text/javascript";
    s.async = true;
    s.src = src;
    s.addEventListener('load', function (e) { self.loaded(e); }, false);
    var head = document.getElementsByTagName('head')[0];
    head.appendChild(s);
}

Usage:

var l = new Loader();
l.require([
"example-script-1.js",
"example-script-2.js"], 
function() {
    // Callback
    console.log('All Scripts Loaded');
    // call your other script here
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM