本文来自 阿里云ARMS前端监控团队
“Script error.” 可能是你遇到的最神秘的错误之一, 最让人抓狂的是这种错误没有提供完整的报错信息(错误堆栈), 让排查无从下手.
产生Script Error的原因
“Script error.” 有时也被称为跨域错误. 当网站请求并执行一个托管在第三方域名下的脚本,就可能抛出 "Script error." 最常见的情况是采用CDN托管JS资源.
为了更好地理解“Script error.”, 假设有如下HTML页面,部署在 test.com 域名下
Test page in http://test.com
复制代码
假定foo方法中的内容如下, 调用了一个未被定义的bar方法
// another-domain.com/app.js
function foo() {
bar(); // ReferenceError: bar is not a function
}
复制代码
页面运行之后,捕获到的异常信息如下:
"Script error.", "", 0, 0, undefined
复制代码
其实这并不是一个JavaScript bug, 基于安全考虑浏览器有意隐藏其它域JS文件抛出的具体错误信息。这样可以有效避免敏感信息无意中被第三方(不受控制的)脚本捕获到,因此,浏览器只允许同域下的脚本捕获具体的错误信息。其它脚本只知道发生了一个错误,而不知具体发生了什么错误。
且看 Webkit源码:
bool ScriptExecutionContext::sanitizeScriptError(String& errorMessage, int& lineNumber, String& sourceURL)
{
KURL targetURL = completeURL(sourceURL);
if (securityOrigin()->canRequest(targetURL))
return false;
errorMessage = "Script error.";
sourceURL = String();
lineNumber = 0;
return true;
}
复制代码
了解了“Script error.”产生的原因, 接下来看看如何解决这类问题。
解法1: 开启CORS跨域资源共享
为了跨域捕获javaScript异常,分两步走
第一步: 添加 crossorigin=”anonymous”属性
复制代码
这一步告诉浏览器,目标脚本通过匿名方式获取。这意味着请求脚本时没有潜在的用户身份信息(如cookies、HTTP 证书等)发送到服务端
第二步: 添加跨域HTTP响应头
Access-Control-Allow-Origin: *
复制代码
或者
Access-Control-Allow-Origin: http://test.com
复制代码
注:大部分主流CDN默认添加了Access-Control-Allow-Origin属性, 如下是阿里CDN的一个示例
$ curl --head https://retcode.alicdn.com/retcode/bl.js | grep -i "access-control-allow-origin"
=> access-control-allow-origin: *
复制代码
完成上述两步之后,跨域脚本的报错就可以通过window.onerror捕获到,回到之前的案例,重新运行之后,捕获到的结果是
=> "ReferenceError: bar is not defined", "http://another-domain.com/app.js", 2, 1, [Object Error]
复制代码
可选解法2: try catch
有时候,不容易往HTTP请求响应头里面添加跨域属性,这时还可以考虑try catch这个候选方案
回到之前的案例
Test page in http://test.com
复制代码
再次运行,输出结果如下
=> ReferenceError: bar is not defined
at foo (http://another-domain.com/app.js:2:3)
at http://test.com/:15:3
=> "Script error.", "", 0, 0, undefined
复制代码
可以看出来, try catch中的console语句输出了完整的信息, 但window.onerror中只能捕获“Script error.” 基于这个特点,可以在catch语句中,将捕获的异常手动上报。
// 参考阿里云ARMS前端监控API指南 https://help.aliyun.com/document_detail/58657.html
__bl.error(error, pos);
复制代码
尽管可以通过try catch能够捕获部分异常,但还是推荐尽量采用方式。