詳解JavaScript錯誤捕獲和上報流程
怎麼捕獲錯誤並且處理,是一門語言必備的知識。在JavaScript中也是如此。
那怎麼捕獲錯誤呢?初看好像很簡單,try-catch就可以了嘛!但是有的時候我們發現情況卻繁多複雜。
-
Q1: 同步可以try-catch,但一個非同步回撥,比如setTimeOut裡的函式還可以try-catch嗎?
-
Q2: Promise的錯誤捕獲怎麼做?
-
Q3: async/await怎麼捕獲錯誤?
-
Q4: 我能夠在全域性環境下捕獲錯誤並且處理嗎?
-
Q5: React16有什麼新的錯誤捕獲方式嗎?
-
Q6: 捕獲之後怎麼上報和處理?
問題有點多,我們一個一個來。
Q1. 同步程式碼裡的錯誤捕獲方式
在同步程式碼裡,我們是最簡單的,只要try-catch就完了
function test1 () { try { throw Error ('callback err'); } catch (error) { console.log ('test1:catch err successfully'); } } test1();
輸出結果如下,顯然是正常的
Q2. 普通的非同步回撥裡的錯誤捕獲方式(Promise時代以前)
上面的問題來了,我們還能通過直接的try-catch在非同步回撥外部捕獲錯誤嗎?我們試一試
// 嘗試在非同步回撥外部捕獲錯誤的結果 function test2 () { try { setTimeout (function () { throw Error ('callback err'); }); } catch (error) { console.log ('test2:catch err successfully'); } } test2();
輸出
注意這裡的Uncaught Error的文字,它告訴我們錯誤沒有被成功捕捉。
為什麼呢? 因為try-catch的是屬於同步程式碼,它執行的時候,setTimeOut內部的的匿名函式還沒有執行呢。而內部的那個匿名函式執行的時候,try-catch早就執行完了。( error的內心想法:哈哈,只要我跑的夠慢,try-catch還是追不上我!)
但是我們簡單想一想,誒我們把try-catch寫到函式裡面不就完事了嘛!
function test2_1 () { setTimeout (function () { try { throw Error ('callback err'); } catch (error) { console.log ('test2_1:catch err successfully'); } }); } test2_1();
輸出結果如下,告訴我們這方法可行
總結下Promise時代以前,非同步回撥中捕獲和處理錯誤的方法
-
在非同步回撥內部編寫try-catch去捕獲和處理,不要在外部哦
-
很多非同步操作會開放error事件,我們根據事件去操作就可以了
Q3. Promise裡的錯誤捕獲方式
可通過Promise.catch方法捕獲
function test3 () { new Promise ((resolve, reject) => { throw Error ('promise error'); }).catch (err => { console.log ('promise error'); }); }
輸出結果
>> reject方法呼叫和throw Error都可以通過Promise.catch方法捕獲
function test4 () { new Promise ((resolve, reject) => { reject ('promise reject error'); }).catch (err => { console.log (err); }); }
輸出結果
>> then方法中的失敗回撥和Promise.catch的關係
-
如果前面的then方法沒寫失敗回撥,失敗時後面的catch是會被呼叫的
-
如果前面的then方法寫了失敗回撥,又沒丟擲,那麼後面的catch就不會被呼叫了
// then方法沒寫失敗回撥 function test5 () { new Promise ((resolve, reject) => { throw Error ('promise error'); }) .then (success => {}) .catch (err => { console.log ('the error has not been swallowed up'); }); } // then方法寫了失敗回撥 function test5 () { new Promise ((resolve, reject) => { throw Error ('promise error'); }) .then (success => {},err => {}) .catch (err => { console.log ('the error has not been swallowed up'); }); }
輸出分別為
1.the error has not been swallowed up
2.無輸出
Q4.async/await裡的錯誤捕獲方式
對於async/await這種型別的非同步,我們可以通過try-catch去解決
async function test6 () { try { await getErrorP (); } catch (error) { console.log ('async/await error with throw error'); } } function getErrorP () { return new Promise ((resolve, reject) => { throw Error ('promise error'); }); } test6();
輸出結果如下
>> 如果被await修飾的Promise因為reject呼叫而變化,它也是能被try-catch的
(我已經證明了這一點,但是這裡位置不夠,我寫不下了)
Q5.在全域性環境下如何監聽錯誤
window.onerror可以監聽全域性錯誤,但是很顯然錯誤還是會丟擲
window.onerror = function (err) { console.log ('global error'); }; throw Error ('global error');
輸出如下
Q6.在React16以上如何監聽錯誤
>> componentDidCatch和getDerivedStateFromError鉤子函式
class Bar extends React.Component { // 監聽元件錯誤 componentDidCatch(error, info) { this.setState({ error, info }); } // 更新 state 使下一次渲染能夠顯示降級後的 UI static getDerivedStateFromError(error) { return { hasError: true }; } render() { } }
有錯誤,那肯定要上報啊!不上報就發現不了Bug這個樣子。Sentry這位老哥就是個人才,日誌記錄又好看,每次見面就像回家一樣
Sentry簡單介紹
Sentry provides open-source and hosted error monitoring that helps all software
teams discover, triage, and prioritize errors in real-time.
One million developers at over fifty thousand companies already ship
better software faster with Sentry. Won’t you join them?
—— Sentry官網
Sentry是一個日誌上報系統,Sentry 是一個實時的日誌記錄和彙總處理的平臺。專注於錯誤監控,發現和資料處理,可以讓我們不再依賴於使用者反饋才能發現和解決線上bug。讓我們簡單看一下Sentry支援哪些語言和平臺吧
在JavaScript領域,Sentry的支援也可以說是面面俱到
參考連結 https://docs.sentry.io/platforms/
Sentry的功能簡單說就是,你在程式碼中catch錯誤,然後呼叫Sentry的方法,然後Sentry就會自動幫你分析和整理錯誤日誌,例如下面這張圖擷取自Sentry的網站中
在JavaScript中使用Sentry
1.首先呢,你當然要註冊Sentry的賬號
這個時候Sentry會自動給你分配一個唯一標示,這個標示在Sentry裡叫做 dsn
2. 安卓模組並使用基礎功能
安裝@sentry/browser
npm install @sentry/browser
在專案中初始化並使用
import * as Sentry from '@sentry/browser'; Sentry.init ({ dsn: 'xxxx', }); try { throw Error ('我是一個error'); } catch (err) { // 捕捉錯誤 Sentry.captureException (err); }
3.上傳sourceMap以方便在線上平臺閱讀出錯的原始碼
// 安裝 $ npm install --save-dev @sentry/webpack-plugin $ yarn add --dev @sentry/webpack-plugin // 配置webpack const SentryWebpackPlugin = require('@sentry/webpack-plugin'); module.exports = { // other configuration plugins: [ new SentryWebpackPlugin({ include: '.', ignoreFile: '.sentrycliignore', ignore: ['node_modules', 'webpack.config.js'], configFile: 'sentry.properties' }) ] };
4. 為什麼不是raven.js?
// 已經廢棄,雖然你還是可以用 var Raven = require('raven-js'); Raven .config('xxxxxxxxxxx_dsn') .install();
Sentry的核心功能總結
捕獲錯誤
try { aFunctionThatMightFail(); } catch (err) { Sentry.captureException(err); }
設定該錯誤發生的使用者資訊
下面每個選項都是可選的,但必須 存在一個選項 才能使Sentry SDK捕獲使用者: id
Sentry.setUser({ id:"penghuwan12314" email: "[email protected]", username:"penghuwan", ip_addressZ:'xxx.xxx.xxx.xxx' });
設定額外資料
Sentry.setExtra("character_name", "Mighty Fighter");
設定作用域
Sentry.withScope(function(scope) { // 下面的set的效果只存在於函式的作用域內 scope.setFingerprint(['Database Connection Error']); scope.setUser(someUser); Sentry.captureException(err); }); // 在這裡,上面的setUser的設定效果會消失
設定錯誤的分組
整理日誌資訊,避免過度冗餘
Sentry.configureScope(function(scope) { scope.setFingerprint(['my-view-function']); });
設定錯誤的級別
在閱讀日誌時可以確定各個bug的緊急度,確定排查的優先書序
Sentry.captureMessage('this is a debug message', 'debug'); //fatal,error,warning,info,debug五個值 // fatal最嚴重,debug最輕
自動記錄某些事件
例如下面的方法,會在每次螢幕調整時完成上報
window.addEventListener('resize', function(event){ Sentry.addBreadcrumb({ category: 'ui', message: 'New window size:' + window.innerWidth + 'x' + window.innerHeight, level: 'info' }); })
Sentry實踐的運用
根據環境設定不同的dsn
let dsn; if (env === 'test') { dsn = '測試環境的dsn'; } else { dsn = '正式環境的dsn'; } Sentry.init ({ dsn });
&n