前言
单页面应用
基于JS构建,如果某个JS加载失败可能导致页面无法显示。
所以需要对加载失败时进行处理,首先需要捕获失败时机
,其次尝试进行重新加载
。
捕获错误
可以通过 script
的 onerror
捕获,但是不适用于框架中,手动写太过繁琐。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script onerror="console.log('出错了'); " src=" http://static.com/1.js"></script>
<script onerror="console.log('出错了'); " src=" http://other-domain.com/2.js"></script>
<script onerror="console.log('出错了'); " src=" http://xxx.com/3.js"></script>
</body>
</html>
可以给 window
添加 error事件
,但需要注意的是,必须确保在加载JS前监听error事件
,而且因为 error事件
不参与冒泡,需要在捕获阶段拿到 error
,即第三个参数为true
。
<script>
window.addEventListener('error', (e) => {
console.log(e);
},true)
</script>
确保捕获到的是JS加载错误
<!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>
window.addEventListener('error', (e) => {
console.log(e);
const tag = e.target
if (tag.tagName === "SCRIPT" && !(e instanceof ErrorEvent)) {
console.log("JS加载失败");
}
}, true)
</script>
</head>
<body>
<script src=" http://static.com/1.js"></script>
<script src=" http://other-domain.com/2.js"></script>
<script src=" http://xxx.com/3.js"></script>
<script>
throw 123
</script>
</body>
</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 domains = ['xxx.com', 'xxx1.com', 'xxx2.com']
const maxRetry = 3
const retryInfo = {}
window.addEventListener('error', (e) => {
const tag = e.target
if (tag.tagName === "SCRIPT" && !(e instanceof ErrorEvent)) {
const url = new URL(tag.src)
// 如果首次加载失败,则重试
if (!retryInfo[url.pathname]) {
retryInfo[url.pathname] = { times: 0, nextIndex: 0 }
}
const info = retryInfo[url.pathname]
if (info.times >= maxRetry) {
console.log(url.pathname + "重试次数已达上限");
return
}
const script = document.createElement('script')
url.host = domains[info.nextIndex]
script.src = url.toString()
document.body.insertBefore(script, tag)
info.times++
info.nextIndex = (info.nextIndex + 1) % domains.length
}
}, true)
</script>
</head>
<body>
<script src=" http://static.com/1.js"></script>
<script src=" http://other-domain.com/2.js"></script>
<script>
throw 123
</script>
</body>
</html>
但是无法保证插入的script执行顺序,需要在每次插入script后阻塞
代码,使用document.write
即可,注意:由于逻辑是在script标签中,所以需要转义,否则编译器可能会误认为script标签结束了。
<!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 domains = ['xxx.com', 'xxx1.com', 'xxx2.com']
const maxRetry = 3
const retryInfo = {}
window.addEventListener('error', (e) => {
const tag = e.target
if (tag.tagName === "SCRIPT" && !(e instanceof ErrorEvent)) {
const url = new URL(tag.src)
// 如果首次加载失败,则重试
if (!retryInfo[url.pathname]) {
retryInfo[url.pathname] = { times: 0, nextIndex: 0 }
}
const info = retryInfo[url.pathname]
if (info.times >= maxRetry) {
console.log(url.pathname + "重试次数已达上限");
return
}
const script = document.createElement('script')
url.host = domains[info.nextIndex]
document.write(`<script src="${url.toString()}">\<\/script>`)
info.times++
info.nextIndex = (info.nextIndex + 1) % domains.length
}
}, true)
</script>
</head>
<body>
<script src=" http://static.com/1.js"></script>
<script src=" http://other-domain.com/2.js"></script>
<script>
throw 123
</script>
</body>
</html>