如何判断script脚本是否加载完成并生成对应状态的Promise
检测Script标签加载状态的可靠实现
该方案不需要依赖「所有动态脚本由自己管控」「静态脚本按顺序执行」这类强假设,兼容静态引入、动态插入的所有带src的外部脚本、内联脚本,直接返回符合预期的Promise实例。
核心判断规则
- 内联脚本(无
src属性):只要存在于DOM树中就必然已经加载执行完成,直接返回已兑现的Promise - 外部脚本:
- 优先通过Performance API判断资源是否已经完成加载:如果对应
src的资源条目存在,说明脚本已经加载完成 - 如果未查询到对应资源条目,说明脚本仍在加载/待加载,为其绑定
load/error事件,在事件触发时兑现Promise - 兼容Performance API不可用的极端场景:绑定事件后通过
setTimeout+readyState检查做兜底,避免事件漏触发
- 优先通过Performance API判断资源是否已经完成加载:如果对应
实现代码
/** * 获取指定script标签加载完成的Promise * @param {HTMLScriptElement} scriptEl 目标script标签 * @returns {Promise<void>} 脚本加载完成时兑现,加载失败时抛出错误 */ function getScriptLoadPromise(scriptEl) { if (!(scriptEl instanceof HTMLScriptElement)) { return Promise.reject(new TypeError('Target must be a HTMLScriptElement')); } // 内联脚本无外部资源,直接返回已兑现状态 if (!scriptEl.src) { return Promise.resolve(); } // 优先通过性能接口判断是否已完成加载 if (window.performance && typeof performance.getEntriesByName === 'function') { const loadedEntries = performance.getEntriesByName(scriptEl.src); if (loadedEntries.length > 0) { return Promise.resolve(); } } // 未命中已加载判断,走事件监听逻辑 return new Promise((resolve, reject) => { let handled = false; const onLoad = () => { if (handled) return; handled = true; scriptEl.removeEventListener('load', onLoad); scriptEl.removeEventListener('error', onError); resolve(); }; const onError = () => { if (handled) return; handled = true; scriptEl.removeEventListener('load', onLoad); scriptEl.removeEventListener('error', onError); reject(new Error(`Script load failed: ${scriptEl.src}`)); }; scriptEl.addEventListener('load', onLoad); scriptEl.addEventListener('error', onError); // 兜底逻辑:兼容性能接口不可用、绑定监听前脚本刚完成加载的极端场景 let checkCount = 0; const fallbackCheck = () => { if (handled) return; checkCount++; // 兼容旧IE的script readyState判断 if (scriptEl.readyState === 'loaded' || scriptEl.readyState === 'complete') { onLoad(); return; } // 最多轮询10次共2秒,超时直接兑现避免永久pending if (checkCount >= 10) { onLoad(); return; } setTimeout(fallbackCheck, 200); }; setTimeout(fallbackCheck, 0); }); }
使用方式
直接遍历document.getElementsByTagName("script")返回的集合,对每个脚本调用该方法即可,可搭配Promise.all等待所有脚本加载完成后执行后续逻辑:
const allScripts = Array.from(document.getElementsByTagName('script')); Promise.all(allScripts.map(script => getScriptLoadPromise(script))) .then(() => { // 所有脚本加载完成后执行的逻辑 console.log('All scripts loaded'); }) .catch(err => { console.error('Script load error:', err); });
边界说明
- 跨域无
Timing-Allow-Origin响应头的脚本,Performance API不会返回详细加载耗时,但资源条目本身依然存在,不影响已加载状态判断 - 动态插入后尚未进入加载队列的脚本,事件监听逻辑会正常等待加载完成触发
data:协议的内联外部脚本、blob协议脚本,加载速度极快时会优先触发load事件,不会出现状态判断错误
内容的提问来源于stack exchange,提问作者Gunther Schadow
相关产品推荐
相关产品推荐

