tech share
  • tech-share
  • Engineering
    • 登录鉴权
    • SSR 页面路由
    • npm 版本号
    • 缓存
    • 数据库容灾
    • 动态效果导出 gif
    • Chrome-devtools
    • C 端 H5 性能优化
    • Docker
    • Monorepo 最佳实践
    • 技术架构演化
    • 项目规范最佳实践
    • snowpack
    • 静态资源重试
    • 前端页面渲染分析
    • Git
    • 前端重构
    • 微前端
    • 项目依赖分析
    • 前端监控原理
    • webpack
    • BS 架构与 CS 架构
    • HTTPS
    • package-lock.json 生成逻辑
    • SVN(Subversion)
    • 数据库分类
    • gulp
    • 前端架构
    • Bundle & Bundless
    • 控制反转 IoC
  • JavaScript
    • Javascript 性能
    • JavaScript 原型(2) - 原型与原型链
    • JavaScript 原型(1) - 构造函数
    • JavaScript - Promise
    • ES6 解构赋值
    • 前端离线化
    • Proxy
    • Object.defineProperty()简介
    • TypeScript
  • MachineLearning
    • GAN生成对抗网络
    • 虚拟对抗训练
    • 深度度量学习
    • 原型网络
    • PyTorch优化器
    • 隐马尔可夫模型2
    • Shapley Value 算法
    • Embarassingly Autoencoder算法
    • AutoRec算法及其后续发展
    • 深度学习常用激活函数
    • 序列预测ConvTran算法
    • 联邦学习
    • 深度学习推荐系统算法整理
    • 隐马尔可夫模型
    • 黎曼优化方法
    • FM算法
    • 机器学习常见评价指标
    • VAE算法
    • Adam优化器详解
    • Transformer算法
    • Self-attention 推荐算法
    • CNN 卷积神经网络
    • 图嵌入
    • 集成学习算法
    • RecBole开源框架
    • NCE-PLRec
    • 深度学习初始化方法
    • RNN循环神经网络
    • PyTorch数据处理
    • PyTorch安装和基本操作
    • XGBoost算法
    • NCF算法与简单MF的对比
    • 计算最佳传输
  • CSS
    • 什么是BFC
    • 纯CSS实现可拖动布局
    • 滚动穿透解决方案
  • React
    • React 生命周期
    • React Ref
    • React Hooks
    • SWR
    • React 数据流
    • React 函数式组件和类组件的区别
  • 可视化
    • OffscreenCanvas
    • Echarts 平滑曲线端点为什么不平滑
    • 颜色空间
    • 词云布局解析
    • 3D 数学基础
    • Canvas 图片处理
    • GLGL ES
    • WebGL 中绘制直线
    • Graphics API
    • 现代计算机图形学基础
    • Canvas 灰度
  • Vue
    • Vue2.x全局挂载整理
    • Vue2.6.x源码阅读
      • Vue2.6.x源码阅读 - 2.目录结构分析
      • Vue2.6.x源码阅读 - 4.源码阅读-platform
      • Vue2.6.x源码阅读 - 1.准备工作
      • Vue2.6.x源码阅读 - 5.源码阅读-core-Vue构造函数
      • Vue2.6.x源码阅读 - 7.源码阅读-core-响应式原理
      • Vue2.6.x源码阅读 - 3.源码阅读-shared
      • Vue2.6.x源码阅读 - 6.源码阅读-core-组件挂载
    • Vue + TypeScript Web应用实践
    • Vue2.x指令
    • nextTick()的使用
    • vue-cli2.x 的使用与项目结构分析
    • Vue响应式原理及总结
    • VueX的使用
    • Electron-Vue + Python 桌面应用实践
    • Vite
    • Vue组件通信整理
    • 记录一个问题的探索过程
  • Linux
    • memcg
  • GameDev
    • 游戏中的几种投影视图
    • 从零开始写软渲染器06
    • 从零开始写软渲染器05
    • 从零开始写软渲染器04
    • 从零开始写软渲染器03
    • 从零开始写软渲染器02
    • 从零开始写软渲染器01
    • 从零开始写软渲染器00
    • 现代游戏常用的几种寻路方案(一)
  • Node
    • NPM Dependency
    • Node 优势
    • Node Stream
    • Node 模块系统
  • HTML
    • html5语义与结构元素
  • 跨端
    • Flutter 介绍
  • Golang
    • Golang 基础
  • AR
    • SceneKit
由 GitBook 提供支持
在本页
  • 如何监控资源加载失败
  • script onerror
  • window.addEventListener
  • 优化资源加载失败
  • 加载失败时,刷新页面 (reload)
  • 针对加载失败的文件进行重加载
  • 始终加载失败该怎么办
  • 总结

这有帮助吗?

  1. Engineering

静态资源重试

上一页snowpack下一页前端页面渲染分析

最后更新于4年前

这有帮助吗?

Web 项目上线后,在千差万别的网络状态中,访问页面难免会遇到前端资源加载失败的情况,占比或许不高,但一遇到,轻则页面样式错乱,重则白屏打不开,影响用户体验感受,紧急情况下甚至影响了用户的工作,属于非常严重的问题。

如何监控资源加载失败

script onerror

我们可以给 script 标签添加上 onerror 属性,这样在加载失败时触发事件回调,从而捕捉到异常。

<script onerror="onError(this)"></script>

并且,借助构建工具 ( 如 webpack 的 script-ext-html-webpack-plugin 插件) ,我们可以轻易地完成对所有 script 标签自动化注入 onerror 标签属性,不费吹灰之力。

new ScriptExtHtmlWebpackPlugin({
  custom: {
    test: /\.js$/,
    attribute: "onerror",
    value: "onError(this)",
  },
});

window.addEventListener

上述方案已然不错,但我们也试想是否可以减少 onerrror 标签大量注入呢?类比脚本错误 onerror 的全局监控方式(详见:),是否也可以通过 window.onerror 去全局监听加载失败呢?

答案否定的,因为 onerror 的事件并不会向上冒泡,window.onerror 接收不到加载失败的错误。冒泡虽不行,但捕获可以!我们可以通过捕获的方式全局监控加载失败的错误,虽然这也监控到了脚本错误,但通过 !(event instanceof ErrorEvent) 判断便可以筛选出加载失败的错误。

window.addEventListener(
  "error",
  (event) => {
    if (!(event instanceof ErrorEvent)) {
      // todo
    }
  },
  true
);

通过监控数据分析,我们发现现实情况不容乐观。访问页面时存在资源加载失败的情况超过了 10000 例/天,且随着页面访问量的上升而增加。

另外,监控资源加载失败的方式不止这些,上述两种方式都属于较好的方案,其他的方式就不再展开。

优化资源加载失败

加载失败时,刷新页面 (reload)

有了监控数据后,便可着手优化。当资源加载失败时,刷新页面可能是最简单直接的尝试恢复方式。于是当监控到资源加载失败时,我们通过 location.reload(true) 强制浏览器刷新重新加载资源,并且为了防止出现一直刷新的情况,结合了 SessionStorage 限制自动刷新次数。

通过监控数据发现,通过自动刷新页面,最终能恢复正常加载占异常总量 30%,优化比例不高,且刷新页面导致了出现多次的页面全白,用户体验不好。

针对加载失败的文件进行重加载

替换域名动态重加载

只对加载失败的文件进行重加载。并且,为了防止域名劫持等导致加载失败的原因,对加载失败文件采用替换域名的方式进行重加载。替换域名的方式可以采用重试多个 cdn 域名,并最终重试到页面主域名的静态服务器上(主域名被劫持的可能性小)

然而,失败资源重加载成功后,页面原有的加载顺序可能发生变化,最终执行顺序发现变化也将导致执行异常。

保证 JS 按顺序执行

在不需要考虑兼容性的情况下,资源加载失败时通过 document.write 写入新的 script 标签,可以阻塞后续 script 脚本的执行,直到新标签加载并执行完毕,从而保证原来的顺序。但它在 IE、Edge 却无法正常工作,满足不了我们项目的兼容性。

于是我们需要增加 “管理 JS 执行顺序” 的逻辑。使 JS 文件加载完成后,先检查所依赖的文件是否都加载完成,再执行业务逻辑。当存在加载失败时,则会等待文件加载完成后再执行,从而保证正常执行。

手动管理模块文件之间的依赖和执行时机存在着较大的维护成本。而实际上现代的模块打包工具,如 webpack ,已经天然的处理好这个问题。通过分析构建后的代码可以发现,构建生成的代码不仅支持模块间的依赖管理,也支持了上述的等待加载完成后再统一执行的逻辑。

// 检查是否都加载完成,如是,则开始执行业务逻辑
function checkDeferredModules() {
  // ...
  if(fulfilled) {
    // 所有都加载,开始执行
    result = **webpack_require**(**webpack_require**.s = deferredModule[0]);
  }
}

然而,在默认情况下,业务代码的执行不会判断配置的 external 模块是否存在。所以当 external 文件未加载完成或加载失败时,使用对应模块将会导致报错。

"react": (function(module, exports) {
  eval("(function() { module.exports = window[\"React\"]; }());");
})

所以我们需要在业务逻辑执行前,保证所依赖的 external 都加载完成。最终通过开发 wait-external-webpack-plugin webpack 插件,在构建时分析所依赖的 external,并注入监控代码,等待所有依赖的文件都加载完成后再统一顺序执行。

至此,针对加载失败资源重试的逻辑最终都通过构建工具自动完成,对开发者透明。重试后存在加载失败的情况优化了 99%。减少了大部分原先加载失败导致异常的情况。

始终加载失败该怎么办

用户网络千变万化,或临时断网、或浏览器突然异常,那些始终加载失败的情况,我们又该如何应对呢? 一个友好的提醒弹框或是最后的稻草,避免用户的无效等待,缓解用户感受。

总结

以上,便是对资源加载失败优化的整体方案,从如何监控加载失败、加载失败时重试、重试失败后的提醒等方面。大幅优化修正了加载失败的问题,也缓解着实遇到异常的用户使用体验。

脚本错误量极致优化-监控上报与 Script error