资源加载失败重载与前端升级检测方案

前言

之前看到了两篇文章,一篇讲连接到网络时重载页面的,一篇讲前端检测升级的,诞生了一些自己的思路和想法,写下了这篇文章。

资源加载失败重载方案

不知道你们是否体验过 Chrome 加载一个网页失败后(网络断开时),自动重载网页的行为(加载成功前以指数级回退的时间间隔尝试重新加载页面),虽然大部分时候可能比较鸡肋,但雀氏比什么都不作为有更好的用户体验,所以我也在自己的站点中加入了这个功能,这里讨论下中间的过程。

资源加载失败的侦测

使用 window.onerror 可以侦听到 js 的运行时错误和资源加载失败错误:

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>
</head>
<body>
  
  <script>
    window.addEventListener('error', () => {
      console.log('error');
    }, true);
  </script>

  <img src="not-found.png" />
  <script src="not-found.js" ></script>

</body>
</html>

控制台输出:

注意: 必须以捕获模式侦听,否则不起作用,另外无法捕获动态导入导致的错误:

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>
</head>
<body>
  
  <script>
    window.addEventListener('error', () => {
      console.log('error');
    }, true);

    import('not-found');
  </script>

</body>
</html>

控制台输出:

可以看到并没有捕获到动态导入的错误,也就无法捕获到主流前端框架路由懒加载时出现的错误,针对动态导入,我们可以使用 PerformanceObserver

PerformanceObserver 是浏览器提供的一个性能观察工具,在影响浏览器性能的时间节点中会触发指定的事件,他的简单使用如下:

js 复制代码
const observer = new PerformanceObserver(function (list, obj) {
  const entries = list.getEntries();
  for (const i = 0; i < entries.length; i++) {
    console.log(entries[i]);
  }
});

observer.observe({ entryTypes: ["resource"] });

{ entryTypes: ["resource"] } 表明我们要观察的性能条目类型集是资源,他的效果如下:

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>
  </head>
  <body>
    <script>
      const observer = new PerformanceObserver(function (list, obj) {
        const entries = list.getEntries();

        for (let i = 0; i < entries.length; i++) {
          console.log(entries[i]);
        }
      });

      observer.observe({ entryTypes: ["resource"] });

      import("./fsdfsdf");
      
    </script>
  </body>
</html>

可以通过 responseStatus 状态码判断是否加载成功:

当然它也可以侦测到资源加载失败,兼容性也还不错,不需要侦测运行时错误的话可以完全替代 window.onerror

关于错误侦测最后扩展两个事件:

rejectionhandled:捕获 rejected 且添加了错误处理程序的 Promise,有个坑点是只能捕获 rejected 后再添加错误处理程序的 Promise,作用不大,比如:

js 复制代码
window.addEventListener('rejectionhandled', () => {
  console.log('rejectionhandled');
});

// 这个无效,无法触发 rejectionhandled
Promise.reject('unknown').catch(() => {});

// 这个有效,触发 rejectionhandled
const p = Promise.reject('unknown');
setTimeout(() => {
  p.catch(() => {});
}, 0);

unhandledrejection:和 rejectionhandled 相反,未被处理的 Promise 错误都会触发此事件,比如:

js 复制代码
window.addEventListener('unhandledrejection', () => {
  console.log('unhandledrejection');
});

// 这个有效,触发 unhandledrejection
Promise.reject('unknown');

// 这个无效,错误被处理了
Promise.reject('unknown').catch(() => {});

错误重载

js 复制代码
function errorHandle() {
  // 离线模式下才进入下一步
  if (!window.navigator.onLine) {
    const _listener = () => {
      window.removeEventListener('online', _listener)

      // 提示用户并延时三秒重载页面
      warning('即将在三秒后重载页面。');
      sleep(3000, () => window.location.reload());
    };

    // 侦听网络连接
    window.addEventListener('online', _listener);
  }
}

const observer = new PerformanceObserver(function (list, obj) {
  const entries = list.getEntries();

  // 判断是否有加载失败的资源,状态码可自定义
  const hasError = entries.some((entrie) => [0, 404].includes(entrie.responseStatus))

  hasError && errorHandle();
});

observer.observe({ entryTypes: ["resource"] });

很简单的策略,只在资源失败且网络离线的情况下添加网络在线侦听事件,并在网络可用时重载页面,这里有个问题是 online 事件触发时网络不一定是可用的,可以替换自己的逻辑来侦听网络连接,比如 navigator.connection

前端升级检测思路

这个思路是基于 service worker,因为之前搞自己的博客对 service worker 有一定的研究,所以提到前端升级检测时就想到了它。

具体的思路是这样的:service worker 被激活后能够拦截作用域内的网络请求,在每次请求时调用 ServiceWorkerRegistration.update() 方法,此方法会促使浏览器立即获取注册的 service worker 脚本并检测是否有升级,如果有升级则开始安装新的 service worker,当 service worker 被安装后还需激活,此时可以提示用户并强制新的 service worker 接管页面。

service worker 检测升级的方式是对前后两个 service worker 脚本进行对比,所以每次打包部署时需要修改 service worker 加班,一般定义一个版本值。

一个简单实现如下:

js 复制代码
// 主线程
if ('serviceWorker' in window.navigator) {
  // 注册 service worker
  window.navigator.serviceWorker
    .register('/sw.js')
    .then((serviceWorker) => {
      if (serviceWorker.waiting) {
        // 新的 service worker 已安装,等待激活,可提示用户
        return;
      }

      // 侦听可更新事件
      serviceWorker.addEventListener('updatefound', () => {
        // 安装中的 service worker
        const installingWorker = serviceWorker.installing;

        // 侦听状态改变事件
        installingWorker?.addEventListener('statechange', () => {
          if (
            installingWorker?.state === 'installed' &&
            navigator.serviceWorker.controller
          ) {
            // 新的 service worker 已安装,等待激活,可提示用户
          }
        });
      });
    });
}

// 侦听更新请求,调用 update 方法检测更新
navigator.serviceWorker.addEventListener("message", async function(event) {
  if (event.data === 'update') {
    const registration = await navigator.serviceWorker.ready;

    registration.update();
  }
});

// --------------- 分割线 ----------------

// sw.js
async function sendUpdate() {
  const clients = await self.clients.matchAll()

  // 向所有客户端发送更新请求
  clients.forEach(function(client) {
    client.postMessage('update');
  });
}

// 侦听网络请求,加入防抖策略防止频繁更新。
let timer = null;
self.addEventListener('fetch', () => {
  if (timer) window.clearTimeout(timer);

  timer = setTimeout(() => {
    sendUpdate();

    timer = null;
  }, 5000);
})

这种方式需要注意使用 cdn 缓存时不缓存 service worker 脚本,另外网络请求不频繁的站点不适合这种方式,比如使用 service worker 预缓存所有站点资源的博客。

--- end

相关推荐
长风清留扬9 分钟前
小程序毕业设计-音乐播放器+源码(可播放)下载即用
javascript·小程序·毕业设计·课程设计·毕设·音乐播放器
web1478621072322 分钟前
C# .Net Web 路由相关配置
前端·c#·.net
m0_7482478023 分钟前
Flutter Intl包使用指南:实现国际化和本地化
前端·javascript·flutter
飞的肖26 分钟前
前端使用 Element Plus架构vue3.0实现图片拖拉拽,后等比压缩,上传到Spring Boot后端
前端·spring boot·架构
青灯文案134 分钟前
前端 HTTP 请求由 Nginx 反向代理和 API 网关到后端服务的流程
前端·nginx·http
m0_7482548838 分钟前
DataX3.0+DataX-Web部署分布式可视化ETL系统
前端·分布式·etl
ZJ_.1 小时前
WPSJS:让 WPS 办公与 JavaScript 完美联动
开发语言·前端·javascript·vscode·ecmascript·wps
GIS开发特训营1 小时前
Vue零基础教程|从前端框架到GIS开发系列课程(七)响应式系统介绍
前端·vue.js·前端框架·gis开发·webgis·三维gis
Cachel wood1 小时前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
学代码的小前端1 小时前
0基础学前端-----CSS DAY9
前端·css