原文作者:Camilo Reyes
■ 原譯:張成文-Ouven
■ 原文地址:http://www.sitepoint.com/proper-error-handling-javascript/
這是關于JavaScript中異常處理的故事。如果你相信墨菲定律,那么任何事情都可能出錯,不,一定會出錯!這篇文章中我們來看下JavaScript中的出錯處理。文章會覆蓋異常處理使用的正反例,然后看下ajax的異步處理。
JavaScript的事件驅動機制讓JavaScript更加豐富,瀏覽器好比就是一個事件驅動的機器,錯誤也是一種事件。當一個錯誤發生時,一個事件就在某個點拋出。理論上,有人會說錯誤是Javascript中的簡單事件。如果你覺得是這樣,那你就要好好去看看了。另外這篇文章只關注瀏覽器端的JavaScript的情況。
這篇文章將在《Exceptional Exception Handling in JavaScript》這篇文章的概念基礎上進行解釋。解釋起來就是,當發生錯誤時,JavaScript會去調用棧檢查異常事件。如果你對此不熟悉建議先去看看基礎的東西。我們的目的是探索處理異常的必要性,接下來你會看到一個 try...catch 塊語句,你要認真思考。
1.例子
例子的代碼在github上,而且最終展示成這樣:
所有的按鈕點擊是都會觸發“炸彈”,這個炸彈模擬了一個拋出的 TypeError異常。下面是這個模塊單元測試的定義:
開始時,這個函數定義了一個空的對象 foo,注意 bar() 沒有在任何地方定義,我們用一個測試用例來看下它是如何引爆炸彈的。
這個單元測試是用mocha和 should.js 寫的。mocha 是一個測試框架,should.js 是一個斷言庫。如果你熟悉它們后,你會感覺寫起來很爽。測試一般使用 it('description') 開始,然后在should 中使用 ` pass/fail` 結束。好消息是測試用例可以在node端運行而不需要瀏覽器。我建議多關注這些測試,因為它們能幫助我們提升代碼的質量。
正如所顯示的, error() 定義了一個空的對象,然后嘗試訪問一個方法,因為 bar() 方法在對象中不存在而會拋出一個異常。使用JavaScript這種動態語言運行一定會出錯。
2.錯誤的方式
對于一些錯誤的處理,我從按鈕的事件中抽離出異常處理的方式,下面是單元測試函數的代碼:
這個處理函數接收一個 fn 回調函數作為輸入,這個函數然后在處理器函數里面被調用,單元測試如下:
如你所見,這個糟糕的處理函數如果有地方出錯就會返回null,回調函數 fn() 可以指向一個正確的方法或者一個異常,下面的點擊處理函數會顯示最終的處理結果。
可惡的是,這里返回了一個null,當我想找哪里出了問題時整個人都蒙逼了。這種失敗沉默的方式會影響用戶體驗和造成數據混亂。更令人崩潰的是,我花了幾個小時來進行debugg,但卻沒有使用 try-catch,這個糟糕的處理函數吞沒了錯誤并認為它沒有問題, 這樣繼續執行下去不會降低代碼質量,但是隱藏的錯誤未來會讓你花幾個小時來debugg。在一個多層的深調用時,基本上不可能發現哪里出了問題。而在這些少數的地方使用 try-catch 是正確的。但是一旦進入錯誤處理函數,就比較糟糕了。
失敗沉默策略會讓你不容易發現錯誤所在,JavaScript提供了一個更優雅的方式來處理這些問題。
3.比較差的方式
繼續,是時候說下一個稍微好點的方法了。我先跳過事件綁定到dom上的部分。這個函數處理和剛剛我們看到的沒什么不同。所不同的是單元測試中它處理異常的方式。
這里定義在原來的基礎上改進了。這里異常事件在調用棧中進行冒泡,我喜歡的是現在錯誤現在會離開方便debugg的調用棧。在這個異常中,解釋器會遍歷整個棧尋找另一個錯誤處理函數。這樣就可以有機會在調用棧的頂端處理這些錯誤。不幸的是,因為這個方法,我不知道錯誤是從哪個地方拋出來的。所以我又得反向遍歷這個棧找到錯誤異常的源頭。但至少我知道某個地方出錯了,并能找到是哪個地方拋出的錯誤。
4.離開調用棧
所以,一個拋出異常處理的方法是直接調用棧的頂端使用 try-catch,就像:
但是,記住我說的瀏覽器是事件驅動的。是的,JavaScript中的錯誤也不過是一個事件。解釋器在當前的執行上下文中執行后釋放。結果是,我們可以利用一個 onerror 的全局異常事件處理函數,它大概是這樣的:
這個處理函數能捕獲任何執行上下文中的錯誤異常。包括任何類型的任何錯誤。而且它能定位到代碼中的錯誤處理。就像其它任何事件一樣,你能捕獲特定錯誤的具體信息。這樣能使異常處理器只專注于一件事情,如果你允許這樣做的話。這些處理函數也可以在任何時候注冊,解釋器會盡可能地遍歷更多的處理函數,我們再也不用使用 try-catch 塊這種帶有瑕疵的debug方式了。尤其是在對待像JavaScript這類事件驅動機制的語言時,onerror的優勢就更大了。
現在我們可以使用全局處理函數來離開棧了,我們可以用來干什么呢。畢竟,調用棧還是存在的。
5.捕獲棧信息
調用棧在定位問題時超級有用。好消息是,瀏覽器提供了這個信息。理所當然,查看錯誤異常中的棧屬性不是標準的一部分,但是只在新的瀏覽器中可以使用。所以,你就可以這樣來把錯誤日志發送給服務器了。
可能從代碼樣例來說不是很明顯,但是上面的代碼一定會出錯。上面提到了,每個處理函數都只處理一個功能。我關心的是這些信息是怎樣被服務器捕獲的。如下:
這些信息來自FireFox 46的開發版本,通過一個正確的錯誤處理函數,記錄了出錯的情況。這里沒必要隱藏錯誤,我可以看到什么地方出現的什么錯誤。這樣代碼debugg就很爽了。這些信息也可以保存在持續化緩存中以便于以后分析。
調用棧對于debugg來說是很有用的,永遠不要低估調用棧的力量。
6.異步處理
處理異步時,JavaScript的異步處理代碼不在當前的指向上下文中,這意味著 try-catch 語句會有問題(不能捕獲到異常):
單元測試的結果如下:
我必須用promise包含這個處理器來獲取這個錯誤。注意的是,一個未被處理的異常發生時,盡管我將代碼使用 try-catch 包含起來了,是的, try-catch 只能在單一的作用域內有效。在一個異常被拋出的同時,解釋器就會從 try-catch 中離開,ajax也是一樣的。所以有兩種選擇,一種是在異步調用里面捕獲異常:
這種方法很有效,但是很多地方可以改進。首先,try-catch 塊在這里用很混亂。實際上,之前是這么做的,但是有問題。另外,V8引擎不鼓勵在函數中使用try-catch(V8 是chrome和nodejs中的JavaScript引擎)。它們的建議是最外層寫這些塊。
所以我們該怎么辦?我說過全局異常處理可以在任何執行上下文中執行,如果給window對象增加一個錯誤處理函數,就OK了。這樣是不是既能處理捕獲處理錯誤又能保持代碼的優雅呢。全局的錯誤處理能讓你的代碼干凈整潔。
下面是服務器收集到的錯誤日志,注意的是如果你使用同樣的代碼在不同瀏覽器上執行,你會看到收集到的日志也是不同的:
這個處理函數甚至告訴我們錯誤是從異步代碼中拋出的嗎,它告訴我們來自 setTimeout() 函數。
7.結論
總的來說,進行異常處理至少有兩種方法。一個是失敗沉默的方法,在錯誤發生時忽略錯誤不作為而不影響后面的繼續執行。另一種是發生后迅速找到錯誤發生的地方。明顯我們知道哪種方法更具有優勢。我的選擇是:不要隱藏錯誤。沒人會因為你代碼中有問題而鄙視你,用戶多試一次是可以接受的。代碼距離完美是很遠的,錯誤也是不可避免的,重要的是你發現錯誤后會怎么做。
譯者注:文章淺顯地分析了錯誤處理的方式和一些正反案例,其實處理錯誤的最終目的還是提供前端代碼的質量,關于錯誤處理上報可以參考下 badjs 的思路,基于現代前端開發模塊化的基礎,使用全局onerror 和 try-catch 相結合的方式更能有效進行錯誤定位。
作者簡介:張成文,英文名Ouven,校導web前端開發工程師,前騰訊web前端工程師,對前端領域的技術知識具有較高的職業能力和探究精神。對前端響應式頁面設計、工程構建組件化、mv*設計實現、前端優化、ES6開發體系、前端開發知識體系等有深入的研究和項目實踐。
頁
這個主題在前邊已經介紹過了,但是沒有納入這個系列,所以,我舊文重發,并作了一些調整。
作為前端開發,你最關注的當然是應該是Javascript Error(下邊簡稱:js error),js error復雜多樣,我大致將其分為三類:捕獲異常、自定異常、未處理異常。捕獲的方法分別為:window.onerror、console.error、window.onunhandledrejection;具體是如何捕獲這些異常的,可以去github上搜索webfunny_monitor看實現方法。好了,看看我是怎么做的吧
線上項目報錯趨勢
首先,我們需要有個報錯趨勢總覽的功能,我們不是機器,不可能一直盯著我們的數據,看看產生了多少錯誤,所以我們需要一個趨勢圖。報錯趨勢圖中添加了對7天前,同一時間段的數據做了對比,可以作為參考。另外,我們還需要一個警報系統,這樣,我們就能夠實時掌握線上項目的健康狀況了。最后,我加了評分功能,輔助我們對項目健康狀況的判斷。
JavaScript錯誤分類聚合
通過分類聚合,我們需要統計出每天js報錯的數量,你的項目有沒有異常,一目了然。通過點擊不同的柱子(選擇不同的日期),展現出某一天的js error分類聚合的結果。幫助我們追溯錯誤產生的具體日期。同時,由于用戶的群體各不相同,使用的設備也是不同的,還需要分析出,每個平臺系統上發生的個數,以及這個錯誤具體影響多少人,最近發生的時間,都是幫我們排查錯誤的重要因素。
js報錯堆棧分析
報錯趨勢分析和歸類完成了,剩下的就是需要找到每個問題的發生的具體位置。所以我提供了三大步驟來完成對js error的精確分析和定位。
1. 報錯發生具體時間,每一種錯誤,我們需要統計出它發生的具體時間,從下圖可以看出,這個錯誤在每個小時,每一分鐘發生的數量都能夠清晰可見。可以讓我們直接定位到錯誤發生的時間,省去查找的麻煩。
js error發生的具體時間
2. sourceMap 反向定位源碼。現在很多監控系統都弄了這個玩意,我也想說太多。通過這項技術,我們可以定位出你的代碼片段,這樣,你就知道哪里發生了錯誤,趁老板還沒發現,趕緊改掉吧。
sourceMap定位源碼
3. 用戶行為記錄復現。好吧,不管你技術有多么牛X,你總會遇到用戶的錯誤,你就是無法復現,真是想死的心都有。不過不用擔心,我們提供了一個功能,叫「查看行為軌跡」,我們可以直接根據這個錯誤查出這個用戶的所有行為記錄,是不是很酷,快來試試吧 ^ _ ^
行為記錄
好了,到這里,我們已經把js erro分析的這么透徹了,還有什么前端報錯是你解決不了的呢?如果還不行,那就趕緊來部署一下吧。:)
本文由 www.webfunny.cn 前端監控系統 提供。 只需要簡單幾步就可以搭建一套屬于自己的前端監控系統,快來試試吧 ^ _ ^。
者:五月君
轉發鏈接:https://mp.weixin.qq.com/s/8f1f3IkOwqK0u5AEuTemlg
*請認真填寫需求信息,我們會在24小時內與您取得聯系。