如何避免使用setTimeout?加载含HTML与JS的混合文件相关问题
问题
加载的是包含HTML和JS的混合文件,而非纯JS文件。目前只能通过setTimeout判断脚本加载完成,但尝试为创建的script元素添加load事件监听未触发,使用DOMContentLoaded事件也无效,希望找到替代setTimeout的方案。
加载的混合文件内容:
<div>HTML part</div> <script> // js part </script>
当前使用的loadVanilla函数:
loadVanilla: async function(arg, parent, callback) { return new Promise((resolve, reject) => { fetch(arg, {}).then(res => {return res.text()}).then(html => { let t = html.split('<script defer>')[1]; let hc = html.split('<script defer>')[0]; let sc = t.split('</script>')[0]; let s = document.createElement('script'); s.innerHTML = sc; if(typeof parent === 'undefined') { document.body.innerHTML += hc; } else { parent.innerHTML += hc; } document.body.appendChild(s); setTimeout(() => { console.log('S LOADED') resolve(`loaded good ${arg} \n `) if(callback) callback() }, 10) }).catch((err) => { reject('bad') }) }) },
尝试无效的事件监听代码:
s.addEventListener('DOMContentLoaded', function() { console.log('S LOADED') resolve(`loaded good ${arg} \n `) if(callback) callback() })
解决方案
无效原因说明
script元素的load事件仅对**外部脚本(通过src引入)**触发,内联脚本不会触发该事件。DOMContentLoaded是整个文档的DOM加载完成事件,和单个脚本的执行完成状态无关,因此监听它无法判断目标脚本是否执行完毕。
分场景处理
场景1:目标脚本是同步执行的
内联脚本插入到DOM后会同步执行,也就是document.body.appendChild(s)执行完成时,脚本已经执行完毕。因此可以直接移除setTimeout,在append之后立即执行resolve和回调:
修改后的关键代码:
document.body.appendChild(s); // 内联脚本已同步执行完成 console.log('S LOADED') resolve(`loaded good ${arg} \n `) if(callback) callback()
场景2:目标脚本包含异步逻辑
如果脚本内部有异步操作(比如fetch、setTimeout),需要让脚本主动通知加载完成:
- 修改混合文件的脚本部分,在异步逻辑完成后调用一个全局回调:
<div>HTML part</div> <script> // js part(包含异步逻辑) async function init() { // 异步操作示例:fetch数据 await fetch('/api/data'); // 执行完所有逻辑后调用全局回调 window.__scriptLoadedCallback?.(); } init(); </script>
- 在
loadVanilla函数中定义全局回调,等待脚本触发:
loadVanilla: async function(arg, parent, callback) { return new Promise((resolve, reject) => { // 定义全局回调函数 window.__scriptLoadedCallback = () => { delete window.__scriptLoadedCallback; // 执行后清理全局变量 console.log('S LOADED') resolve(`loaded good ${arg} \n `) if(callback) callback() }; fetch(arg, {}) .then(res => res.text()) .then(html => { let t = html.split('<script defer>')[1]; let hc = html.split('<script defer>')[0]; let sc = t.split('</script>')[0]; let s = document.createElement('script'); s.innerHTML = sc; // 插入HTML部分 if(typeof parent === 'undefined') { document.body.innerHTML += hc; } else { parent.innerHTML += hc; } // 插入脚本 document.body.appendChild(s); }) .catch((err) => { delete window.__scriptLoadedCallback; // 出错时也要清理全局变量 reject('bad') }) }) },
内容的提问来源于stack exchange,提问作者Nikola Lukic
相关产品推荐
相关产品推荐

