常見錯誤的分類javascript
對於用戶在訪問頁面時發生的錯誤,主要包括如下幾個類型:css
一、js運行時錯誤html
JavaScript代碼在用戶瀏覽器中執行時,因爲一些邊界狀況、本地環境的不可控等因素,可能會存在js運行時錯誤。前端
而依賴客戶端的某些方法,因爲兼容性或者網絡等問題,也有機率會出現運行時錯誤。vue
e.g: 下圖是當使用了未定義的變量"foo",致使產生js運行時錯誤時的上報數據:java
二、資源加載錯誤react
這裏的靜態資源包括js、css以及image等。如今的web項目,每每依賴了大量的靜態資源,並且通常也會有cdn存在。ios
若是某個節點出現問題致使某個靜態資源沒法訪問,就須要可以捕獲這種異常並進行上報,方便第一時間解決問題。git
e.g: 下圖是圖片資源不存在時的上報數據:github
三、未處理的promise錯誤
未使用catch捕獲的promise錯誤,每每都會存在比較大的風險。而編碼時有可能覆蓋的不夠全面,所以有必要監控未處理的promise錯誤並進行上報。
e.g: 下圖是promise請求接口發生錯誤後,未進行catch時的上報數據:
四、異步請求錯誤(fetch與xhr)
異步錯誤的捕獲分爲兩個部分:一個是傳統的XMLHttpRequest,另外一個是使用fetch api。
像axios和jQuery等庫就是在xhr上的封裝,而有些狀況也可能會使用原生的fetch,所以對這兩種狀況都要進行捕獲。
e.g: 下圖是xhr請求接口返回400時捕獲後的上報數據:
各個類型錯誤的捕獲方式
一、window.onerror與window.addEventListener('error')捕獲js運行時錯誤
使用window.onerror和window.addEventListener('error')都能捕獲,可是window.onerror含有詳細的error堆棧信息,存在error.stack中,因此咱們選擇使用onerror的方式對js運行時錯誤進行捕獲。
window.onerror =function(msg, url, lineNo, columnNo, error){// 處理錯誤信息}// demomsg: UncaughtTypeError: UncaughtReferenceError: a is not definederror.statck:TypeError:ReferenceError: a is not defined at http://xxxx.js:1:13window.addEventListener('error', event => (){// 處理錯誤信息},false);// true表明在捕獲階段調用,false表明在冒泡階段捕獲。使用true或false均可以,默認爲false
二、資源加載錯誤使用addEventListener去監聽error事件捕獲
實現原理:當一項資源(如<img>或<script>)加載失敗,加載資源的元素會觸發一個Event接口的error事件,並執行該元素上的onerror()處理函數。
這些error事件不會向上冒泡到window,不過能被window.addEventListener在捕獲階段捕獲。
但這裏須要注意,因爲上面提到了addEventListener也可以捕獲js錯誤,所以須要過濾避免重複上報,判斷爲資源錯誤的時候才進行上報。
window.addEventListener('error', event => (){// 過濾js errorlettarget = event.target || event.srcElement;letisElementTarget = targetinstanceofHTMLScriptElement || targetinstanceofHTMLLinkElement || targetinstanceofHTMLImageElement;if(!isElementTarget)returnfalse;// 上報資源地址leturl = target.src || target.href;console.log(url);},true);
三、未處理的promise錯誤處理方式
實現原理:當promise被reject而且錯誤信息沒有被處理的時候,會拋出一個unhandledrejection。
這個錯誤不會被window.onerror以及window.addEventListener('error')捕獲,可是有專門的window.addEventListener('unhandledrejection')方法進行捕獲處理。
window.addEventListener('rejectionhandled', event => {// 錯誤的詳細信息在reason字段// demo:settimeout errorconsole.log(event.reason);});
四、fetch與xhr錯誤的捕獲
對於fetch和xhr,咱們須要經過改寫它們的原生方法,在觸發錯誤時進行自動化的捕獲和上報。
改寫fetch方法:
// fetch的處理function_errorFetchInit(){if(!window.fetch)return;let_oldFetch =window.fetch;window.fetch =function(){return_oldFetch.apply(this,arguments) .then(res=>{if(!res.ok) {// 當status不爲2XX的時候,上報錯誤}returnres; })// 當fetch方法錯誤時上報.catch(error=>{// error.message,// error.stack// 拋出錯誤而且上報throwerror; }) }}
對於XMLHttpRequest的重寫:
xhr改寫
// xhr的處理function_errorAjaxInit(){letprotocol =window.location.protocol;if(protocol ==='file:')return;// 處理XMLHttpRequestif(!window.XMLHttpRequest) {return; }letxmlhttp =window.XMLHttpRequest;// 保存原生send方法let_oldSend = xmlhttp.prototype.send;let_handleEvent =function(event){try{if(event && event.currentTarget && event.currentTarget.status !==200) {// event.currentTarget 即爲構建的xhr實例// event.currentTarget.response// event.currentTarget.responseURL || event.currentTarget.ajaxUrl// event.currentTarget.status// event.currentTarget.statusText}); } }catch(e) {vaconsole.log('Tool\'s error: '+ e); } } xmlhttp.prototype.send =function(){this.addEventListener('error', _handleEvent);// 失敗this.addEventListener('load', _handleEvent);// 完成this.addEventListener('abort', _handleEvent);// 取消return_oldSend.apply(this,arguments); }}
關於responseURL 的說明
須要特別注意的是,當請求徹底沒法執行的時候,XMLHttpRequest會收到status=0 和 statusText=null的返回,此時responseURL也爲空string。
另外在安卓4.4及如下版本的webview中,xhr對象也不存在responseURL屬性。
所以咱們須要額外的改寫xhr的open方法,將傳入的url記錄下來,方便上報時帶上。
var_oldOpen = xmlhttp.prototype.open;// 重寫open方法,記錄請求的urlxmlhttp.prototype.open =function(method, url){ _oldOpen.apply(this,arguments);this.ajaxUrl = url;};
其餘問題
一、其餘框架,例如vue項目的錯誤捕獲
vue內部發生的錯誤會被Vue攔截,所以vue提供方法給咱們處理vue組件內部發生的錯誤。
Vue.config.errorHandler =function(err, vm, info){// handle error// `info` 是 Vue 特定的錯誤信息,好比錯誤所在的生命週期鉤子// 只在 2.2.0+ 可用}
二、script error的解決方式
"script error.」有時也被稱爲跨域錯誤。當網站請求並執行一個託管在第三方域名下的腳本時,就可能遇到該錯誤。最多見的情形是使用 CDN 託管 JS 資源。
其實這並非一個 JavaScript Bug。出於安全考慮,瀏覽器會刻意隱藏其餘域的 JS 文件拋出的具體錯誤信息,這樣作能夠有效避免敏感信息無心中被不受控制的第三方腳本捕獲。
所以,瀏覽器只容許同域下的腳本捕獲具體錯誤信息,而其餘腳本只知道發生了一個錯誤,但沒法獲知錯誤的具體內容。
解決方案1:(推薦)
添加 crossorigin="anonymous" 屬性。
此步驟的做用是告知瀏覽器以匿名方式獲取目標腳本。這意味着請求腳本時不會向服務端發送潛在的用戶身份信息(例如 Cookies、HTTP 證書等)。
添加跨域 HTTP 響應頭:
Access-Control-Allow-Origin: *
或者
Access-Control-Allow-Origin: http://test.com
注意:大部分主流 CDN 默認添加了 Access-Control-Allow-Origin 屬性。
完成上述兩步以後,便可經過 window.onerror 捕獲跨域腳本的報錯信息。
解決方案2
難以在 HTTP 請求響應頭中添加跨域屬性時,還能夠考慮 try catch 這個備選方案。
在以下示例 HTML 頁面中加入 try catch:
<!doctype html>Test page in http://test.com// app.js裏面有一個foo方法,調用了不存在的bar方法window.onerror =function(message, url, line, column, error){console.log(message, url, line, column, error); }try{ foo(); }catch(e) {console.log(e);throwe; }// 運行輸出結果以下:=> ReferenceError: bar is not definedat foo (http://another-domain.com/app.js:2:3)at http://test.com/:15:3=> "Script error.", "", 0, 0, undefined
可見 try catch 中的 Console 語句輸出了完整的信息,但 window.onerror 中只能捕獲「Script error」。根據這個特色,能夠在 catch 語句中手動上報捕獲的異常。
總結
上述的錯誤捕獲基本覆蓋了前端監控所需的錯誤場景,可是第三部分指出的兩個其餘問題,目前解決的方式都不太完美。
對於有使用框架的項目:一是須要有額外的處理流程,好比示例中就須要單獨爲vue項目進行初始化;二是對於其餘框架,都須要單獨處理,例如react項目的話,則須要使用官方提供的componentDidCatch方法來作錯誤捕獲。
而對於跨域js捕獲的問題:咱們並不能保證全部的跨域靜態資源都添加跨域 HTTP 響應頭;而經過第二種包裹try-catch的方式進行上報,則須要考慮的場景繁多而且沒法保證沒有遺漏。
雖然存在這兩點不足,但前端錯誤捕獲這部分仍是和項目的使用場景密切相關的。咱們能夠在瞭解這些方式之後,選擇最適合本身項目的方案,爲本身的監控工具服務。
—— —— 參考文檔 —— ——
1.Using XMLHttpRequest:
https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest
2.script error 產生的緣由和解決辦法:
https://www.alibabacloud.com/help/zh/faq-detail/88579.htm
3.JavaScript執行錯誤:
https://docs.fundebug.com/notifier/javascript/type/javascript.html
4.betterjs的script error:
https://github.com/BetterJS/badjs-report/issues/3
5.Vuejs的errorHandler:
https://cn.vuejs.org/v2/api/index.html#errorHandler
6.React的componentDidCatch:
https://reactjs.org/blog/2017/07/26/error-handling-in-react-16.html