最完备的懒加载错误兜底方案,再也不会白屏了!

2022-07-13 14:16:58 浏览数 (1)

为了优化首屏加载渲染速度,减小首屏包体积,项目中很多代码是通过懒加载动态导入(dynamic import)的。

但是在使用时并没有对动态导入的失败做处理,我们通过项目的监控平台发现了上百例因「包资源下载失败导致的页面白屏」,用户无法正常使用。

该情况通常只会在慢网或者 CDN 故障的时候出现,在开发过程中不会注意到这种边界场景

因此,需要一个机制来兜底动态导入失败的场景。

动态导入的使用

使用 webpack 的项目,通常使用动态导入的方法是:

代码语言:javascript复制
() => import('./path/to/component')

动态导入会返回一个 promise 对象,并且导入成功时这个 promise 需要 resolve 一个具有默认导出(default exprot)的模块,但是 reject 并没有得到处理。

以 React 为例,通常我们搭配 React.lazy 来使用动态导入,React.lazy 接受一个返回 promise 的函数。

代码语言:javascript复制
const OurComponent = React.lazy(() => import('./OurComponent'));

本文虽然是以 React 为例,但在 Vue 项目的动态导入也可以使用下方的优化方案。

动态导入失败时,lazy 不会隐式处理异常。

所以,我们需要在中间额外引入一层捕获异常并处理。

异常处理

这一层需要做的事有:

  1. 成功时需要返回一个具有默认导出的模块
  2. 失败时捕获错误并上报日志
代码语言:javascript复制
function componentLoader(componentImport) {
  return React.lazy(() => {
      return new Promise(resolve => {
        componentImport()
         .then(resolve)
         .catch(e => {
           // 上报错误日志
         });
      });
  })
}

在这里,有些同学可能会使用比较粗暴的做法,catch 到错误后执行 location.reload() 让页面刷新。

对网络请求这些可能由于非代码逻辑问题导致错误的功能,最好的做法是「增加重试机制提升稳定性」

如果是慢网的问题,我们需要能进行重试

代码语言:javascript复制
function retry(componentImport, retryLeft) {
  return new Promise(resolve => {
    componentImport()
      .then(resolve)
      .catch(e => {
        if (retryLeft > 1) { // 尝试重新加载
          retry(componentImport, retryLeft - 1).then(resolve);
        } else {
          // 上报错误日志
          return;
        }
      });
  });
}


function lazyRetry(componentImport, retryLeft = 3) {
  return React.lazy(() => retry(componentImport, retryLeft));
}

引入后使用方式变更为:

代码语言:javascript复制
const OurComponent = lazyRetry(() => import('./OurComponent'));

如果是 CDN 故障,我们需要能换 CDN 重试

webpack 懒加载的原理,是在需要时,向 dom 插入一个 script 标签,在 script 加载成功时(onload)调用动态导入 promise 的 resolve,并带上加载的资源,在失败时(onerror)调用 reject。

webpack 懒加载源码

换 CDN 的原理其实就是在 scriptA 加载失败后插入新的 scriptB,scriptC ... 重新加载。

所以在 scriptA 加载失败时,「要让原本的 onerror 不执行」,避免让 promise 改态(因为 promise 是不可逆的),「将本来该执行的 onerror 赋给 scriptB」

因为生产环境会使用 mini-css-extract-plugin 将样式单独提取为一个 css 文件,所以样式加载失败时需要做类似 script 的处理,「也不能触发 link 标签的 onerror」

https://github.com/Nikaple/assets-retry 这个 package 实现了「无侵入式的静态资源自动重试」,它的原理是 hook 原生的 document.createElementscript.onerror 等方法,不侵入原生逻辑的同时插入重试相关代码,并捕获监听 document 的 error 事件做相应处理,我们可以在项目中直接引入这个包来实现 CDN 重试。

仍然无法加载回资源

有了以上的处理,但资源仍然无法加载回来,此时错误并不会抛出,只是页面上不展示资源对应的功能,用户仍然可以正常使用页面,不会白屏。

总结

通过针对业务优化场景中遇到的懒加载失败问题,我们分析了 webpack 源码,借助了 import() 网络重试加载机制,提高了程序的鲁棒性,降低前端白屏率,一定程度上提升了用户体验,对于前端工程的收益较为明显。


❤️感谢

如果本文对你有帮助,点赞

0 人点赞