静态文件加载超时引起的Bug

2,733 阅读3分钟

背景

某天,测试说,你们这个网站哦,pc上打开很快,手机上很慢,快看看啥问题。作为甩锅第一名,当然不承认是前端问题了,第一反应是服务端慢。但是呢,pc上很快,这又说不通了。手机端也看不到请求,pc上又正常,第一反应是去看看日志,结果日志没问题。思考了很久,想到手机上用的是wifi,pc上没用,所以就用pc连了wifi看看,果然,都很慢!所以是wifi的锅(不是)。

pc上看了请求,发现是某个js文件请求一直pending状态,直到超时(大约40s)。仔细一看,这个js是外部js,最近才加的,所以锅就是你了!

问题解析

好了,正经说原因。我们有网络隔离,pc上用了代理proxy,wifi没有,是内网。项目为react单页面应用,由于业务原因,在入口index.html文件里加载了所需的js文件https://res.xxxx.com/aut.js。所以终端打开页面时,请求外部js文件时,出现了网络不通的情况,导致超时。

坑!!!
该文件是域名指定的,所以dns首先会解析域名,由于域名是正规有效的,所以不会立即返回5xx服务端错误。终端就会等待加载,但实际上内网情况下,是加载不出来的,所以会一直超时。pc有代理,所以能够成功加载。

尝试阶段

  • 异步加载
    页面打不开,其实就是css、dom没有加载出来,首先想到的是把js放到后面加载。尝试了如下方法:
    <script defer="defer" type="text/javascript" src="https://res.xxxx.com/aut.js">
    <script async="async" type="text/javascript" src="https://res.xxxx.com/aut.js">
    window.onload
    document.write

结果是不行的。原因在于,我们是单页面应用,页面实际出来的效果其实是路由加载出来的。而我们的js,在最外层的index.html中执行的,因而实际的执行顺序是index.html加载 -> script执行 -> 路由加载实际页面

  • alert超时取消
    这是病急乱投医搜到的,说是利用alert来取消js的加载。我记得alert是能阻塞加载,但不能取消啊…
    不过我没有研究过,只是试了下这个方法,还是不行,参考链接和核心代码放下面了
    alert取消js加载
<script type="text/javascript">
var jsLoaded = false;
setTimeout("callback()", 2000);
function callback() {
    if (!jsLoaded) {
        alert("Javascript not loaded after 2 seconds!");
    } else {
        alert('Loaded');
    }
}
</script>
<script type="text/javascript" id="foo" src="url.js" onload="jsLoaded=true"></script>
  • WebWorker
    既然延迟加载不行,那么,如果有另一个线程来执行,不影响主线程,那不就行了?答案就是web workerweb worker 是运行在后台的 JavaScript,独立于其他脚本,不会影响页面的性能

解决方法

上代码

    <script id="worker" type="app/worker">
       (function handler() {
        // importScripts引入外部js
        this.importScripts('https://res.xxxx.com/aut.js');
      })();
    </script>
    <script>
          // 1.创建inline的js
          var blob = new Blob([document.querySelector('#worker').textContent], {
            type: 'application/javascript',
          });
          // 2.创建一个url对象
          var url = URL.createObjectURL(blob);
          // 3.使用这个url对象创建Worker
          var worker = new Worker(url);
          // 4.释放这个url对象,ie的话执行了该方法,会导致worker里的函数来不及执行
          // URL.revokeObjectURL(url);
    </script>
  1. 由于Worker的作用只是去加载一个外部js文件,所以没有另外写worker.js,而是采用inline的形式,利用new Blob([js执行代码],{type: 'application/javascript',})来生成inline js文件
  2. 利用URL.createObjectURL(blob)创建inline js的url,传给Worker的构造函数。由于URL.createObjectURL每次都会新建一个url,所以需要利用URL.revokeObjectURL及时销毁
  3. worker.js中,使用importScripts引入外部js文件即可
  4. 注意用于生成inline js的script片段,它的type必须是一个浏览器无法识别的值,这里是app/worker