常见异常
‘xxx’ is undefined
造成 ‘xxx’ is undefined
的主要原因有两种:
-
其实大家自己写代码的时候,现在各种Lint工具都可以帮我们规避掉 ‘xxx’ is undefined
这类问题,但为什么还是会出现呢?常见的一个情况就是后台接口返回的数据是JSON格式,加上某个后端服务一旦出现了问题,导致返回的数据异常。这是遇到的最多的一个情况。
-
大家现在的应用里面应该都有分包的工作,一般都会把一些主体的包单独拆出来,一旦这种包加载失败,就会出现 ‘xxx’ is undefined
。
数据校验
后端接口的数据不可信,该做的判断一定不能少。在引入一些其他的优化方案的时候,要考虑一下它的副作用。可能这个问题不是你导致的,但是别人的问题可能会导致你的问题!
CDN劫持
我们有个业务在没有任何发布的情况下,然后它的页面加载的失败率从0.5%到了10%。我们就找CDN的同学去看哪里出了问题,CDN也没异常。后来几经波折,多方定位,发现某一个节点上面的文件指纹和主栈的CDN节点的文件指纹是不一样的。也就是说这个节点的CDN文件被篡改了,被劫持了。大家知道CDN实际上是主栈要推到各个CDN栈上去的。虽然在我们的页面上面是走HTTPS,但是从主栈把资源推到各个CDN上的时候,为了追求速度是不会去走HTTPS的,而是走HTTP。其实这就给了一些运营商可趁之机,在这个阶段就把CDN上的资源做了一个篡改,导致资源上了HTTPS还是被劫持。
当然,我们肯定知道会存在这种劫持情况。所以很早就加了一个SRI的方案,做防劫持的处理。简单讲一下,比如SRI会在Script标签上面有一个指纹,当你通过构建工具生成一个JS文件的加密后的一个指纹,然后在服务器下载下来的文件上面,也会对那个文件做一个指纹,然后再和这个标签上面的文件做指纹的比对。如果发现不一样,就会认为这个文件被篡改了,是被劫持的。当然防劫持的目的达到了,但同时也带来了新的问题——白屏。因为浏览器认为资源不可信,就不会去执行JS了,不会执行这些JS就不会去做任何异步尝试,也就说JS最终还是被当作加载失败来处理了。
当SRI失败的时候,还是会触发Script Error事件。所以我们要做一些补充的方案,如果Script触发onError,我们会向主栈拉取一次JS资源,不是CDN节点上的JS,然后拉取主栈的JS的话,我们会取他前头部的几个字节以及尾部的字节,和我们现有的CDN上的资源做一个对比,全文对比太长了,我们做前后的这种字符检测就可以了。如果检查字符不一样,我们就直接使用主栈的资源进行加载,然后再走到上报的平台。
<script
type="text/javascript"
src="..."
integrity="sha256-xxx sha384-xxx"
crossorigin="anonymous"
onerror="loadScriptError.call(this, event)"
onsuccess="loadScriptSuccess"
></script>
Script Error
- iOS中跨域的异步脚本的报错信息在window.onError中是捕获不到的。
举个例子,在a域名的页面下引入了b域名的脚本,b域名的脚本在执行setTimeout中的一段代码,出现了异常。window.onError是无法获取到这个错误的。目前针对这种情况的解决方法,只能对异步脚本中的代码手动地抛出错误进行捕获。
- 通过native代码执行的脚本报错,是无法被捕获的。
对于Hybird的APP中一些复杂的页面,客户端都会去调一些我们JS的代码,执行一些能力。客户端同学可能网上搜了一段代码,并不会告诉你这段代码有什么,然后本地跑执行没问题,所以他就直接加上去了。但比如说他回调你页面里面的一个接口,windows怎么xxx执行一下。但其实可能你代码有什么问题,或者是他在你的代码声明之前就执行了,这个时候实际上也会有报错且没有Script Error,所以这种问题你就需要联合客户端这边,要对他们执行的这种JS代码做一些保护,常见的就是执行前,先对JS回调进行检查。
因为一些离线缓存实现的问题不太对,现在一些大的App里面,都有做这种离线缓存功能。理论上来说,把接口拦截掉,返回一个本地的资源,当然资源的Header是客户端来设置的,这个时候不会设置跨域头,也会报Script Error 。为了解决这个问题,我们做了一个临时策略,把离线包里面所有的资源都换成主域的资源,解决历史版本中的问题,同时推动客户端添加缓存头。