Skip to content

脚本加载失败如何重试?

思路

需要考虑两个问题:

  1. 什么时间点重试?
  2. 如何重试?

先考虑第一个问题,什么时间点重试?

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 事件不会冒泡,所以需要在捕获阶段监听。


再考虑第二个问题,如何重试?

  1. 准备一份备用域名列表
  2. 记录每个脚本当前尝试的备用域名索引
  3. 当脚本加载失败时,替换域名并重新插入脚本

完整示例

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>

基于 MIT 许可发布