Reputation: 337
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