一套完善的前端體系應少不了異常統計與監控,即便有足夠的質量保證體系,不免會出現一些意料以外的事,尤爲是在複雜的網路環境和運行環境之下。爲了保證代碼的健壯性以及頁面的穩定性,咱們從多個方面來作異常的防範和監控。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 捕獲異常等,並給出一個實際工做中的示例。