当我有一个 Javascript 函数包含一个外部 Javascript 文件时,我怎样才能使函数块直到外部文件完全加载?

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?

首先声明这需要在纯香草Javascript中完成,没有第 3 方库或框架强调不是JQuery).

假设我有一个名为 included_script.js 的 JS 文件,其中包含以下内容:

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

现在假设我有以下简化的 JS 函数,它加载外部 JS 文件并尝试执行其中定义的 sayIt 函数:

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). */
}

请注意,在将 externalScript 附加到头部之前,我已经尝试过 externalScript.setAttribute("defer", "defer")externalScript.setAttribute("async", "async")(尽管我知道这是多余的)等等。另请注意,回调不可行。

如何在上面显示的 "BLOCK HERE" 部分制作函数 loadIt 块,直到 externalScript (included_script.js)完全下载到客户端,使得externalScript(included_script.js)中定义的sayIt函数在从函数loadIt?[=36调用时实际工作=]

更新基于 BOBRODES 的精彩、简单的回答:

included_script.js还有以下内容:

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

loadIt 现在变成了 class(它比这复杂得多,但这显示了它工作所需的基本机制):

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);
                }
            }
        );
    }
}

现在,在我的主要代码中,我可以执行以下操作,并确保函数 sayIt 在调用之前已加载并准备好使用。

从异步函数内部:

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

从异步函数外部:

var loader = new loadIt();

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

sayIt();

效果很好——正是我所追求的。谢谢,鲍勃!

附带说明一下,我知道有一种猖獗且目光短浅的 "blocking is always evil in every case imaginable, and can never, ever, under any circumstances, result in anything good" 心态,但我不同意当大量数据时阻塞是不好的-正在生成驱动的 GUI,这取决于多个自定义 classes,这些自定义 classes 又相互依赖 and/or 其他 classes/resources/scripts -- 特别是当呈现的 GUI 元素具有多个事件处理程序时(onclickoninputonfocus 等)期望这些 class 实例及其方法的 existence/usability。

如果你不能使用回调,那么使用promises,它旨在在异步环境中创建一个"blocking"机制,而无需添加单独的回调函数。