主题
脚本加载失败如何重试?
思路
需要考虑两个问题:
- 什么时间点重试?
- 如何重试?
先考虑第一个问题,什么时间点重试?
html
<!-- 为每个 script 监听 onerror 事件 -->
<body>
<script src="example1.js" onerror="handleError()"></script>
<script src="example2.js" onerror="handleError()"></script>
<script src="example3.js" onerror="handleError()"></script>
</body>html
<head>
<script>
// 统一监听 error
window.addEventListener(
'error',
(e) => {
// 过滤非脚本加载错误,保证后续的逻辑只针对于脚本加载错误
if (e instanceof ErrorEvent || e.target.tagName !== 'SCRIPT') {
return
}
// 处理重试逻辑 ...
},
true, // 由于脚本的 error 事件不冒泡,需要使用捕获阶段
)
</script>
</head>
<body>
<script src="example1.js"></script>
<script src="example2.js"></script>
<script src="example3.js"></script>
</body>由于现代前端工程几乎都要使用打包工具(如 Vite、Webpack 等),所以不建议使用方案1,因为手动在每个 <script> 标签上添加 onerror 处理器,既不优雅也不现实。
方案2 通过监听全局 error 事件来捕获所有脚本加载失败的情况,更加通用和优雅。需要注意的是,脚本的 error 事件不会冒泡,所以需要在捕获阶段监听。
再考虑第二个问题,如何重试?
- 准备一份备用域名列表
- 记录每个脚本当前尝试的备用域名索引
- 当脚本加载失败时,替换域名并重新插入脚本
完整示例
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script>
// 备用域名列表
const backupDomains = [
'example1.com',
'example2.com',
'example3.com',
// ...
]
// 记录每个脚本当前尝试的备用域名索引
const nextDomain = {}
// 统一监听 error
window.addEventListener(
'error',
(e) => {
// 过滤非脚本加载错误,保证后续的逻辑只针对于脚本加载错误
if (e instanceof ErrorEvent || e.target.tagName !== 'SCRIPT') {
return
}
// 替换域名
const url = new URL(e.target.src)
const pathname = url.pathname
if (!nextDomain[pathname]) {
nextDomain[pathname] = 0
}
const index = nextDomain[pathname]
if (index >= backupDomains.length) {
return // 所有备用域名都尝试过了,停止尝试
}
const domain = backupDomains[index]
url.hostname = domain
const newUrl = url.toString()
document.write(`<script src="${newUrl}"><\/script>`) // 重新写入脚本标签
nextDomain[pathname]++
},
true, // 由于脚本的 error 事件不冒泡,需要使用捕获阶段
)
</script>
</head>
<body>
<script src="http://example.com/static/script1.js"></script>
<script src="http://example.com/static/script2.js"></script>
<script src="http://example.com/static/script3.js"></script>
</body>
</html>