2

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.

John Doe
  • 327
  • 1
  • 2
  • 8
  • 1
    in this simplistic case, `externalScript.onload = sayIt;` more complex case, `externalScript.addEventListener('load', function(e) { /* do things here */});` – Jaromanda X Aug 07 '18 at 03:23
  • Possible duplicate of [Pure JavaScript equivalent of jQuery's $.ready() - how to call a function when the page/DOM is ready for it](https://stackoverflow.com/questions/9899372/pure-javascript-equivalent-of-jquerys-ready-how-to-call-a-function-when-t) – Obsidian Age Aug 07 '18 at 03:23
  • The function `sayIt` is just actually a way of demonstrating that I want everything defined in `included_script.js` to be immediately available; I may or may not be calling its resources immediately. This will be a quasi dependency loader. When I load a dependency, I want everything in it available to immediately following lines. Some dependencies, and other things requiring them, will be dynamically loaded at run time, based on various logic. – John Doe Aug 07 '18 at 03:25
  • 1
    Don't block... you'll be breaking all sorts of stuff. Fix your code. – Brad Aug 07 '18 at 03:25
  • `based on various logic` - well, only you know this logic, so there's not much can be done for you – Jaromanda X Aug 07 '18 at 03:33
  • 1
    If you can't use callbacks, then use [promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), which are designed to create a "blocking" mechanism in an asynchronous environment without having to add a separate callback function. – BobRodes Aug 07 '18 at 03:42
  • @BobRodes: Yes! `await` [for a promise return] before moving on to the next line is perfect. Please turn your comment into an answer and I'll accept it. – John Doe Aug 07 '18 at 05:13

1 Answers1

1

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.

BobRodes
  • 5,990
  • 2
  • 24
  • 26