全部产品
阿里云办公

Script Error 的产生原因和解决方法

更新时间:2018-08-23 12:39:12

“Script error”是一个常见错误,但由于该错误不提供完整的报错信息(错误堆栈),问题排查往往无从下手。本文分析了该错误的产生原因,并提出了可行的解决方法。

“Script error”的产生原因

“Script error”有时也被称为跨域错误。当网站请求并执行一个托管在第三方域名下的脚本时,就可能遇到“Script error”。最常见的情形是使用 CDN 托管 JS 资源。

为了更好地理解,假设以下 HTML 页面部署在 http://test.com 域名下:

  1. <!doctype html>
  2. <html>
  3. <head>
  4. <title>Test page in http://test.com</title>
  5. </head>
  6. <body>
  7. <script src="http://another-domain.com/app.js"></script>
  8. <script>
  9. window.onerror = function (message, url, line, column, error) {
  10. console.log(message, url, line, column, error);
  11. }
  12. foo(); // 调用app.js中定义的foo方法
  13. </script>
  14. </body>
  15. </html>

假设 foo 方法调用了一个未定义的 bar 方法:

  1. // another-domain.com/app.js
  2. function foo() {
  3. bar(); // ReferenceError: bar is not a function
  4. }

页面运行之后,捕获到的异常信息如下:

  1. "Script error.", "", 0, 0, undefined

其实这并不是一个 JavaScript Bug。出于安全考虑,浏览器会刻意隐藏其他域的 JS 文件抛出的具体错误信息,这样做可以有效避免敏感信息无意中被不受控制的第三方脚本捕获。因此,浏览器只允许同域下的脚本捕获具体错误信息,而其他脚本只知道发生了一个错误,但无法获知错误的具体内容。

请参考 Webkit 源码

  1. bool ScriptExecutionContext::sanitizeScriptError(String& errorMessage, int& lineNumber, String& sourceURL)
  2. {
  3. KURL targetURL = completeURL(sourceURL);
  4. if (securityOrigin()->canRequest(targetURL))
  5. return false;
  6. errorMessage = "Script error.";
  7. sourceURL = String();
  8. lineNumber = 0;
  9. return true;
  10. }

了解了“Script error”的产生原因之后,接下来看看如何解决这个问题。

解法 1:开启 CORS(Cross Origin Resource Sharing,跨域资源共享)

为了跨域捕获 JavaScript 异常,可执行以下两个步骤:

  1. 添加 crossorigin="anonymous" 属性。

    1. <script src="http://another-domain.com/app.js" crossorigin="anonymous"></script>

    此步骤的作用是告知浏览器以匿名方式获取目标脚本。这意味着请求脚本时不会向服务端发送潜在的用户身份信息(例如 Cookies、HTTP 证书等)。

  2. 添加跨域 HTTP 响应头。

    1. Access-Control-Allow-Origin: *

    或者

    1. Access-Control-Allow-Origin: http://test.com

    注意:大部分主流 CDN 默认添加了 Access-Control-Allow-Origin 属性。以下是阿里 CDN 的示例:

    1. $ curl --head https://retcode.alicdn.com/retcode/bl.js | grep -i "access-control-allow-origin"
    2. => access-control-allow-origin: *

完成上述两步之后,即可通过 window.onerror 捕获跨域脚本的报错信息。回到之前的案例,页面重新运行后,捕获到的结果是:

  1. => "ReferenceError: bar is not defined", "http://another-domain.com/app.js", 2, 1, [Object Error]

解法 2(可选):try catch

难以在 HTTP 请求响应头中添加跨域属性时,还可以考虑 try catch 这个备选方案。

在之前的示例 HTML 页面中加入 try catch

  1. <!doctype html>
  2. <html>
  3. <head>
  4. <title>Test page in http://test.com</title>
  5. </head>
  6. <body>
  7. <script src="http://another-domain.com/app.js"></script>
  8. <script>
  9. window.onerror = function (message, url, line, column, error) {
  10. console.log(message, url, line, column, error);
  11. }
  12. try {
  13. foo(); // 调用app.js中定义的foo方法
  14. } catch (e) {
  15. console.log(e);
  16. throw e;
  17. }
  18. </script>
  19. </body>
  20. </html>

再次运行,输出结果如下:

  1. => ReferenceError: bar is not defined
  2. at foo (http://another-domain.com/app.js:2:3)
  3. at http://test.com/:15:3
  4. => "Script error.", "", 0, 0, undefined

可见 try catch 中的 Console 语句输出了完整的信息,但 window.onerror 中只能捕获“Script error”。根据这个特点,可以在 catch 语句中手动上报捕获的异常。

  1. // 参考本文末尾的相关文档“前端监控 API 使用指南”
  2. __bl.error(error, pos);

注意:尽管 try catch 方法可以捕获部分异常,但推荐采用解法 1。

相关文档