【发布时间】:2017-02-18 11:10:10
【问题描述】:
在我的项目中,我有非常标准的 loadScript 方法来动态加载 JS 脚本,完成后会触发回调:
app.utilities.loadScript = function loadScript(url, callback)
{
if (app.scripts[url]===true) {
callback();
} else {
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = url;
script.onload = function() {
app.scripts[url] = true;
callback();
}
// Fire the loading
head.appendChild(script);
}
};
在我的项目情况下,我正在使用基于组件的系统。为了说明这个问题,想象在同一个页面上有两个 Google Maps 组件。两者都是组件的实例。在他们的构造函数中,我正在调用该方法,就像这样......
constructor(selector) {
super(selector);
// API load status
this._api = false;
// load the maps API
app.utilities.loadScript('https://maps.googleapis.com/maps/api/js?v=3.exp&key=' + app.config.mapsKey, this.initMap.bind(this));
}
我预料到如果一个组件的两个实例加载相同的脚本,它不应该被加载两次。出于这个原因,您可以看到在 loadScript 帮助程序中,我如何维护一个已加载脚本的全局数组:app.scripts[url] = true;
但是,此策略不起作用,因为多个 loadScript 调用彼此非常接近地启动。由于第一个调用尚未完成(正在进行中),第二个调用开始另一个调用。
我正在考虑立即在全局数组中的脚本上设置“加载”状态,但这仍然让我想知道第二个调用如何监听第一个调用准备就绪,因为它确实需要这样的触发器回调触发得太快了。
我觉得我可能想多了?
编辑:包括解决此问题的代码,基于@siam 的回答,并添加了一些调整:
app.utilities.loadScriptEvent = function loadScript(url, eventName)
{
if (app.scripts[url] == 'loading') {
return true;
}
else {
app.scripts[url] = 'loading';
var event = new CustomEvent(eventName);
// Adding the script tag to the head as suggested before
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = url;
// Then bind the event to the callback function.
// There are several events for cross browser compatibility.
//script.onreadystatechange = callback;
script.onload = function() {
app.scripts[url] = true;
app.window.dispatchEvent(event);
}
// Fire the loading
head.appendChild(script);
}
};
请注意,该方法现在命名为 loadScriptEvent,并且第二个参数是事件名称,而不是回调。这个想法是,即使两个组件几乎可以同时调用它,它们都侦听相同的单个事件。但这还不够,如果第一个请求已经忙于加载脚本,则必须停止第二个请求。我已经意识到加载状态。你可以这样调用这个方法:
app.utilities.loadScriptEvent('https://maps.googleapis.com/maps/api/js?v=3.exp&key=' + app.config.mapsKey, "gmloaded");
app.window.addEventListener('gmloaded',this.initMap.bind(this));
这很好用。组件实例彼此不知道,但脚本仍然只加载一次,异步的,我们可以在它完成后立即运行代码。
【问题讨论】:
-
使用
setInterval检查第一个请求是否完成,如果完成则启动第二个请求并清除该间隔 -
@siam。我害怕这样的答案,但这可能是唯一的方法。请注意,虽然第二个请求不知道第一个请求(因为这是两个彼此不知道的组件实例)。
-
嗯...也许还有另一种方法。您可以创建一个在第一个请求完成时触发的自定义事件。您还可以通过该事件传递第一个请求的
data。 -
@siam 将进一步探索该选项,谢谢!
标签: javascript concurrency callback