John Doe
John Doe

Reputation: 337

When I have a Javascript function include an external Javascript file, how can I make the function block until the external file is completely loaded?

Let me preface this by stating that this needs to be done in pure, vanilla Javascript, with no 3rd-party libraries or frameworks (emphatically not JQuery).

Say I have a JS file, named included_script.js, with the following content:

function sayIt() {
    alert("Hello!");
}

Now say I have the following simplified JS function that loads the external JS file and attempts to execute the sayIt function defined therein:

function loadIt() {
    var externalScript = document.createElement("script");
    externalScript.type = "text/javascript";
    externalScript.src = "js/included_script.js";
    document.getElementsByTagName("head")[0].appendChild(externalScript);

    /* BLOCK HERE, and do not continue until externalScript
    (included_script.js) has been completely loaded from the server
    and included into the document, so that the following execution of 'sayIt'
    actually works as expected. */

    sayIt();   /*I expect the "Hello!" alert here, but 'sayIt' is undefined (which
    I think - but am not 100% sure - is because this line is reached before
    externalScript (included_script.js) is fully downloaded from the server). */
}

Note that before appending externalScript to the head I have tried things like externalScript.setAttribute("defer", "defer"), externalScript.setAttribute("async", "async") (even though I know this is redundant), and et cetera. Note also that callbacks are not feasible for use.

How can I make function loadIt block at the "BLOCK HERE" part shown above until externalScript (included_script.js) is completely downloaded to the client, so that the sayIt function defined in externalScript (included_script.js) actually works when called from function loadIt?

UPDATE BASED ON BOBRODES' BRILLIANT, SIMPLE ANSWER:

included_script.js still has the following content:

function sayIt() {
    alert("Hello!");
}

loadIt is now turned into a class (it's a lot more complex than this, but this shows the bare-bones mechanics required for it to work):

function loadIt() {
    this.loadExternal = async function() {
        return new Promise(
            function(resolve, reject) {
                try {
                    var externalScript = document.createElement("script");
                    externalScript.type = "text/javascript";
                    externalScript.src = "js/included_script.js";

                    if (externalScript.readyState) {
                        externalScript.onreadystatechange = function() {
                            if (externalScript.readyState == "loaded" ||
                                externalScript.readyState == "complete") {
                                externalScript.onreadystatechange = null;
                                resolve(true);
                            }
                        };
                    } else {
                        externalScript.onload = function() {
                            resolve(true);
                        };
                    }

                    document.getElementsByTagName("head")[0].appendChild(externalScript);
                }
                catch(err) {
                    reject(err);
                }
            }
        );
    }
}

Now, in my main code, I can do the following, with it being guaranteed that function sayIt is loaded and ready for use before it's invoked.

From inside an async function:

var loader = new loadIt();
await loader.loadExternal();
sayIt();

From outside an async function:

var loader = new loadIt();

(async function() {
    await loader.loadExternal();
})().catch(err => {
    console.error(err);
});

sayIt();

This works beautifully -- exactly what I was after. Thanks, Bob!

As a side note, I know there is a rampant and short-sighted "blocking is always evil in every case imaginable, and can never, ever, under any circumstances, result in anything good" mentality, but I disagree that blocking is bad when a heavily-data-driven GUI is being generated, which depends on multiple custom classes that, in-turn, depend on each other and/or other classes/resources/scripts -- especially when the rendered GUI elements have multiple event handlers (onclick, oninput, onfocus, etc.) that expect the existence/usability of instances of these classes and their methods.

Upvotes: 2

Views: 54

Answers (1)

BobRodes
BobRodes

Reputation: 6165

If you can't use callbacks, then use promises, which are designed to create a "blocking" mechanism in an asynchronous environment without having to add a separate callback function.

Upvotes: 1

Related Questions