
本文介绍一种基于 async/await 的现代 javascript 方案,通过动态创建 <script> 元素并链式等待 onload 事件,确保多个外部脚本严格按序加载、同步顶层代码执行完毕,且前一个完全就绪后下一个才启动,最终统一触发 alljsloaded() 回调。
本文介绍一种基于 async/await 的现代 javascript 方案,通过动态创建 <script> 元素并链式等待 onload 事件,确保多个外部脚本严格按序加载、同步顶层代码执行完毕,且前一个完全就绪后下一个才启动,最终统一触发 alljsloaded() 回调。
在前端工程实践中,仅靠 <script defer> 或为最后一个 <script> 标签硬编码 onload="allJsLoaded()" 并不可靠——前者不保证脚本内异步逻辑(如 fetch、setTimeout、模块初始化 Promise)真正完成;后者更存在严重缺陷:onload 仅表示脚本已下载并执行完顶层同步代码,无法感知其内部异步任务是否结束,且将回调耦合在 HTML 属性中违背关注点分离原则,也难以做错误处理与重试。
✅ 正确解法是构建一个可等待的串行加载流程,核心思想是:每个脚本的加载与执行完成,必须是一个可 await 的 Promise。以下是推荐实现:
/**
* 动态加载单个脚本,并返回 Promise
* @param {string} url 脚本 URL
* @returns {Promise<void>}
*/
function loadScript(url) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = url;
script.type = 'text/javascript';
script.async = false; // 关键:禁用 async,确保执行顺序(配合 append 顺序)
script.onload = () => resolve();
script.onerror = () => reject(new Error(`Failed to load script: ${url}`));
document.head.appendChild(script);
});
}
/**
* 串行加载多个脚本,全部完成后调用 allJsLoaded()
* @param {string[]} urls 脚本 URL 数组(按执行顺序)
*/
async function loadScriptsInOrder(urls) {
try {
for (const url of urls) {
await loadScript(url);
console.log(`✅ Script loaded and executed: ${url}`);
}
allJsLoaded(); // 所有脚本均完成加载 + 同步执行
} catch (err) {
console.error('❌ Script loading failed:', err);
// 可选:触发错误回调、上报监控、降级处理
}
}
// 使用示例
const scriptUrls = [
'/js/utils.js',
'/js/config.js',
'/js/main.js'
];
loadScriptsInOrder(scriptUrls);? 关键要点说明:
async = false 不可省略:虽然 <script> 默认异步,但显式设为 false 可避免因浏览器优化导致的非预期并行行为;更重要的是,我们依赖 appendChild 的 DOM 插入顺序与 onload 触发顺序的一致性。
Comprehensive Three.js 3D graphics reference下载详细的 Three.js 3D 图形参考,涵盖场景设置、相机、几何体、材质、光照、动画、控制器、加载器、数学工具和调试。
-
onload ≠ “脚本彻底完成”:它仅代表浏览器已解析并执行了该脚本的顶层同步代码。若 file2.js 中包含 setTimeout(() => { window.init = true; }, 100),onload 触发时 window.init 尚未赋值。因此,脚本自身需主动暴露“就绪信号”(例如导出 ready Promise 或挂载 __READY__ 标志),并在 loadScript 中扩展等待逻辑:
// 增强版 loadScript(支持等待脚本内部 ready 信号) async function loadScriptWithReady(url, readyCheck = () => true) { await loadScript(url); // 等待脚本声明的就绪条件(如 window.myLib?.isReady === true) await waitForCondition(readyCheck, 5000); // 最大等待 5s } function waitForCondition(fn, timeout = 3000) { return new Promise((resolve, reject) => { const start = Date.now(); const check = () => { if (fn()) return resolve(); if (Date.now() - start > timeout) return reject(new Error('Ready condition timeout')); requestAnimationFrame(check); }; check(); }); } -
替代方案对比:
- import() 动态导入:适用于 ES 模块,天然支持 await import('./a.js'); await import('./b.js');,但要求脚本为有效模块且同源/CORS 配置正确;
- defer + DOMContentLoaded:适合静态声明场景,但无法动态控制、无错误捕获、不感知模块内异步逻辑;
- window.load / DOMContentLoaded:全局事件,无法精准关联到特定脚本集,且 load 等待所有资源(图片、CSS),严重延迟。
✅ 总结:动态创建 + async/await + 显式 Promise 化 onload 是目前最可控、可调试、可扩展的串行加载范式。它兼顾兼容性(IE11+)、健壮性(错误可捕获)、可维护性(逻辑集中、易于单元测试),并为后续集成加载状态监控、超时重试、并发数限制等高级能力预留清晰接口。

















