一套完善的前端体系应少不了异常统计与监控,即便有足够的质量保证体系,不免会出现一些意料以外的事,尤为是在复杂的网路环境和运行环境之下。为了保证代码的健壮性以及页面的稳定性,咱们从多个方面来作异常的防范和监控。javascript
对于咱们操做的数据,尤为是由 API 接口返回的,时常会有一个很复杂的深层嵌套的数据结构。为了代码的健壮性,不少时候须要对每一层访问都做空值判断,就像这样:html
props.user && props.user.posts && props.user.posts[0] && props.user.posts[0].comments && props.user.posts[0].comments[0]
相似的代码你们可能都写过,没写过大概也见到别人写过。看起来确实至关地不美观,有句话说得很棒:前端
The opposite of beautiful is not ugly, but wrong.java
咱们得找到一种,更简单、更优雅、更安全的方式来处理这种情形。参考这篇文章:Safely Accessing Deeply Nested Values In JavaScript,文章提到借助 Ramda、Lenses、Lodash 以及 Immutable.js 等类库的方式,并提供一个很是简洁明了的原生解决方案:node
function getIn(p, o) { return p.reduce(function(xs, x) { return (xs && xs[x]) ? xs[x] : null; }, o); }
接下来咱们这样访问就能够了:git
getIn(['user', 'posts', 0, 'comments'], props)
若是正常访问到,则返回对应的值,不然返回 null
。github
这里提供的只是主动防护的一种情形,关于如何编写更安全的代码这里不做深刻展开。npm
浏览器提供 window.onerror
API 来帮助咱们进行全局的错误监控:浏览器
window.onerror()
`<img>
或 <script>
)加载失败,能被单一的 window.addEventListener
捕获/** * @param {String} message 错误信息 * @param {String} source 发生错误的脚本URL * @param {Number} lineno 发生错误的行号 * @param {Number} colno 发生错误的列号 * @param {Object} error Error对象 */ window.onerror = function(message, source, lineno, colno, error) { // ... }
其中 error 对象包含详细的错误堆栈信息,在 IE9 之前,没有这个参数。安全
能够经过 try..catch 来主动抓取错误,想要对一段代码 try..catch,咱们能够这样:
try { // ... } catch (error) { handler(error) }
对一个函数作 try..catch 封装:
function tryify(func) { return function() { try { return func.apply(this, arguments) } catch (error) { handleError(error) throw error } } }
方案已经明确,但还有一些问题。在查看 JavaScript 错误统计时,发现 80% 以上都是 "script error"。原来,当加载自不一样域的脚本中发生语法错误时,为避免信息泄露,语法错误的细节将不会报告,而代之简单的 "Script error."
而在大多数状况下,咱们的静态资源放在专门的 CDN 服务器上,跟站点并不在一个域,因此若是只是简单的抓取,只会获得一堆意义不大的 script error
解决方案:
须要作两点:
1.在 script 便签添加 crossorigin,默认值 crossorigin="anonymous"
<script src="//xxx.com/example.js" crossorigin></script>
在 require.js 里提供一个 onNodeCreated hook,供咱们提供扩展,要添加 crossorigin 属性,以下所示:
<script> // 若是在 require.js 加载以前定义了 requirejs,require.js 会将其做为一个对象传入 requirejs.config var requirejs = { onNodeCreated: function(node, config, id, url) { node.setAttribute('crossorigin', 'anonymous') } } </script> <script src="//xxx.com/require.js" charset="utf-8"></script>
在 2.2.0 版本以上可用(很遗憾的是,目前的集成解决方案版本恰好低于这个版本)。
2.同时在 CDN 服务器增长响应头 access-control-allow-orgin
,配置容许访问 CORS 的域,不然浏览器直接将禁止加载。
这一点上面也有提到,算是一种比较通用,可定制强的方案。固然,在性能上也会有一些损耗。
综合考虑,try..catch 通用性更好,但因为其在性能方面的一些损耗,CORS 优于 try..catch
随后,介绍一个 JavaScript stack trace 的小工具:https://github.com/CurtisCBS/... ,工具由 Curtis 和 mirreal 共同完成。
主要是用于捕获页面 JavaScript 异常报错,捕获异常类型包含:
使用方式也很简单,但使用 script mode 引入文件后,调用 init 函数,进行初始化配置和监听
<script src="//unpkg.com/jstracker@latest/dist/jstracker.js"></script> <script> jstracker.init({ delay: 1000, maxError: 10, sampling: 1, report: function(errorLogs) { console.table(errorLogs) } }) </script>
若是是使用 module mode,以下:
// npm install jstracker --save import jstracker from 'jstracker' jstracker.init({ concat: false, report: function(errorLogs) { // console.log('send') } })
若是要使用 try..catch 捕获,jstracker 暴露出一个 tryJS
对象,能够处理 try..catch 包装,就像这样:
import jstracker from 'jstracker'; this.handleSelect = jstracker.tryJS.wrap(this.handleSelect);
全部错误信息统一由 report 函数处理,能够在此之上作数据处理:
// ubt.js import jstracker from 'jstracker'; import utility from 'utility'; jstracker.init({ concat: false, report: function(errorLogs) { const errorLog = errorLogs[0]; errorLog.ua = window.navigator.userAgent; ubtTracker.send(errorLog); } }); const ubtTracker = { key: { UBT_JS_TRACKER: 'xxxx-xxxx-xxxx' }, send(data) { const value = utility.deserializeUrl(data); xxxx.send(['trace', this.key.UBT_JS_TRACKER, value]); } }; function wrapContext(ctx) { for (const func in ctx) { ctx[func] = jstracker.tryJS.wrap(ctx[func]); } } export { wrapContext, ubtTracker, jstracker };
做为开发者以及项目维护者的身份,咱们应当编写更安全健壮的代码。但因为环境的多样性,不管再完善的测试,code review 都不免都所疏漏,咱们须要一套监控系统来完善整个前端体系。
在监控的时候,出于同源安全策略没法拿到准确的错误信息,在此,有两种解决方案:
最后,咱们对整个监控工做封装了一个基础的核心,能够监控 JavaScript Runtime 异常,资源加载异常,以及 try..catch 捕获异常等,并给出一个实际工做中的示例。