异步调用场景下如何按顺序加载动态JavaScript文件?
保证动态脚本按顺序加载的解决方案(无需数组缓存)
异步加载动态脚本时,浏览器可能因并行加载特性打乱脚本的加载与执行顺序(例如a2.js和a3.js的加载顺序颠倒),进而引发运行异常。以下针对无法使用数组缓存的场景,提供三种可行的解决方案:
方案1:基于Promise链式调用实现串行加载
利用SYS.SCRIPT.LOAD方法返回Promise的特性,维护一个串行加载的Promise链,确保每个脚本只有在前一个完全加载执行完成后才开始加载。
修改前端监听逻辑:
// 初始化一个已完成的Promise作为链的起点 let loadSequence = Promise.resolve(); LOCAL_COMMUNICATOR.on("RETURN", function(action, data) { if (action == "LOAD_SCRIPT") { console.log("LOAD_SCRIPT: " + data); // 将新的加载任务追加到Promise链,严格保证顺序 loadSequence = loadSequence.then(() => SYS.SCRIPT.LOAD(data)); } })
原理:每次加载任务都依赖前一个任务的完成状态,从逻辑上强制脚本按接收顺序串行加载执行,无需额外缓存数组。
方案2:通过Node.js与前端的交互确认控制顺序
从通信源头控制脚本发送时机:Node.js仅在收到前一个脚本加载完成的确认后,才发送下一个脚本的加载指令。
Node.js端修改:
function GET_FILES_NAME(path, name_of_go, main_length) { fs.readdir(path, function(err, items) { let currentScriptIndex = 0; // 发送下一个脚本的函数 function sendNextScript() { if (currentScriptIndex >= items.length) return; const targetScript = items[currentScriptIndex]; console.log("VISUAL SCRIPT FOR POST LOAD ::::::::::", targetScript); io.sockets.emit( "RETURN", "LOAD_SCRIPT", "cache/" + name_of_go + "/" + targetScript ); currentScriptIndex++; } // 监听前端加载完成的确认信号,触发下一个脚本发送 io.sockets.on("SCRIPT_LOADED", sendNextScript); // 启动第一个脚本的发送 sendNextScript(); }); }
前端LOAD方法修改:
export var SCRIPT = { SCRIPT_ID: 0, LOAD: async function addScript(src, isAsync) { return new Promise((resolve, reject) => { var s = document.createElement("script"); if (typeof isAsync !== 'undefined') s.type = "module"; s.onload = function () { SCRIPT.SCRIPT_ID++; console.log('\x1b[36m%s\x1b[0m', "Script id loaded [" + SCRIPT.SCRIPT_ID + "] : " + this.src ); var filename = this.src.substring( this.src.lastIndexOf("/") + 1, this.src.lastIndexOf(".") ); filename = filename.replace(".", "_"); // 向Node.js发送加载完成的确认信号 LOCAL_COMMUNICATOR.emit("SCRIPT_LOADED"); resolve('Async js code block loaded and executed.'); console.log('LOaded -> ' + filename) }; s.setAttribute("src", src); document.body.appendChild(s); }) } };
原理:通过双向通信的确认机制,从发送端保证脚本的顺序,前端只需按接收顺序处理即可,无需缓存。
方案3:关闭脚本异步加载特性(非模块脚本适用)
对于非模块类型的脚本,设置async=false,强制浏览器按照脚本插入DOM的顺序加载执行。
修改前端LOAD方法:
export var SCRIPT = { SCRIPT_ID: 0, LOAD: async function addScript(src, isAsync) { return new Promise((resolve, reject) => { var s = document.createElement("script"); if (typeof isAsync !== 'undefined') { s.type = "module"; } else { // 关闭异步加载,强制按插入顺序执行 s.async = false; } s.onload = function () { SCRIPT.SCRIPT_ID++; console.log('\x1b[36m%s\x1b[0m', "Script id loaded [" + SCRIPT.SCRIPT_ID + "] : " + this.src ); var filename = this.src.substring( this.src.lastIndexOf("/") + 1, this.src.lastIndexOf(".") ); filename = filename.replace(".", "_"); resolve('Async js code block loaded and executed.'); console.log('LOaded -> ' + filename) }; s.setAttribute("src", src); document.body.appendChild(s); }) } };
注意:模块脚本(type="module")默认异步加载,此方案对模块脚本不生效,需使用方案1或2。
内容的提问来源于stack exchange,提问作者Nikola Lukic
相关产品推荐
相关产品推荐

