目錄
引子
最近想起這方面的事情,就去花時間查找了相關資料,以下是個人的總結。
異常類型
從使用瀏覽器,瀏覽一個網頁,與網頁進行交互的過程,從用戶的角度想一想會出現那些異常。
首先是使用瀏覽器一般都是基於操作系統,系統自身可能會出現問題,比如內存不夠。這類情況歸為系統異常。
正常打開瀏覽器后,訪問網頁的時候,可能沒有網絡,或提示出現服務錯誤等等,這類情況歸為網絡異常。
能夠正常訪問網頁后,用戶進行交互時,可能出現一種情況下點擊有效,另一種情況下點擊無效。這類情況歸為應用異常。
在上面感性的認知基礎上,下面進一步進行細化。
系統異常
系統異常情況比較少,相關的可能有:
- 瀏覽器崩潰
網絡異常
網絡異常中,相關的可能有:
XMLHttpRequest
請求異常Fetch
請求異常- 靜態資源加載異常
應用異常
應用異常可以用 JavaScript 中的錯誤對象體現出來:
- EvalError : 與
eval()
有關的錯誤。 - RangeError : 表示這個值不在允許值集或范圍內。
- ReferenceError : 表示發現一個無效的引用。
- SyntaxError : 表示發生了解析錯誤。
- TypeError :當其它類型錯誤都不符合時,TypeError 用於指示一個不成功的操作。
- URIError :表示用於處理 URI 的函數(encodeURI 或 decodeURl)使用方式與其定義的不兼容。
比較常見的異常可以參考 Top 10 JavaScript errors from 1000+ projects 。
異常捕獲
瀏覽器都具有某種向用戶報告異常的機制,對於用戶都是隱藏此類信息。對於開發者,一般在控制台可以看到相關信息。
下面看下捕獲異常對應的方法。
try-catch 捕獲
try-catch 使用的形式如下:
try {
// 可能導致異常的代碼
} catch(error) {
// 發生異常時的處理
}
測試頁面見這里,有下面的一些特點:
catch
塊中會接收一個包含異常信息的對象,在不同的瀏覽器中包含的信息可能不同,但共同有一個保存異常信息的message
屬性。- 不能捕獲語法異常。
- 不能捕獲異步異常。
- 該方式捕獲的異常,不會出現在控制台上,也不會被
error
事件捕獲。
語法異常在開發的階段就很容易發現,例如:
try {
var num = '333;
} catch(error) {
console.info('try-catch:', error);
}
不能捕獲異步異常示例如下:
try {
setTimeout(() => {
name.forEach(() => {});
},1000)
} catch(error) {
console.info('try-catch:', error);
}
try-catch 比較適合用在那些可以預見可能出錯的地方。
error 事件捕獲
通常將這個事件綁定在 window
上,但由於歷史原因,使用 DOM 不同級別的綁定方式,會有些差別。測試頁面見這里
DOM0 級方式
也就是使用 window.onerror
指定處理程序,其特點有:
- 參數有 5 個,對應含義分別為
message
— 錯誤信息(字符串)、source
— 發生異常的腳本URL(字符串)、lineno
— 發生異常的行號(數字)、colno
— 發生異常的列號(數字)、error
— Error 對象。 - 函數體內用
return true
可以阻止異常信息出現在控制台。 - 可以捕獲到異步異常。
- 不能捕獲到語法異常。
- 不能捕獲
try-catch
中的異常。 - 不能捕獲
script
、img
、input
、audio
、source
、track
標簽元素src
屬性的加載異常(HTML5 不支持的frame
標簽不討論)。測試頁面見這里。 - 不能捕獲
link
標簽的加載異常,測試頁面間這里這里。
DOM2 級方式
也就是使用 window.addEventListener
指定處理程序,其特點有:
- 參數對應 1 個,是一個
ErrorEvent
對象,其中包含信息相對 DOM0 級更加豐富。 - 函數體內用
preventDefault()
方法可以阻止異常信息出現在控制台,但如果是加載資源異常無法阻止。 - 可以捕獲
script
、img
、input
、audio
、source
標簽元素src
屬性的加載異常(track
嘗試了一下不行)。由於加載請求不會冒泡,所以需要在事件的捕獲階段監聽才行。但這種方式無法知道 HTTP 的相關狀態。測試頁面見這里。 - 可以捕獲
link
標簽的加載異常,測試頁面間這里這里。 - 可以捕獲異步異常。
- 不能捕獲到語法異常。
- 不能捕獲到
try-catch
中的異常。
onerror
事件比較適合捕獲預料之外的異常。
Promise、Async/Await 異常捕獲
Async
函數返回的總是一個 Promise
,如果 Async
函數里面有異常,Promise
會 reject
。所以統一的放到 Promise
里面的 catch
處理會比較方便。其特點:
- 沒有寫
catch
的Promise
無法被onerror
或try-catch
捕獲,測試頁面見這里。 - 當
Promise
被reject
且沒有reject
處理器的時候,會觸發unhandledrejection
事件 unhandledrejection 。 - 寫了
catch
后,不會觸發unhandledrejection
事件,所以建議全局增加這個事件監聽。 - 本地測試驗證的時候,注意跨域的限制,見 issues1、issues2。
XMLHttpRequest 請求異常捕獲
XMLHttpRequest 目前來說應用非常廣泛,有對應的 error
事件可以進行監聽。在實際中,有可能團隊內已有約定的一套異常狀態碼,可根據實際情況進行對應的處理。這是簡單示例 。
Fetch 請求異常捕獲
Fetch API 提供了一個獲取資源的接口。它對於使用過 XMLHttpRequest 的人來說會很類似,但這個新的接口提供了更加強大的功能。有下面的一些點需要注意:
fetch
方法返回的是一個Promise
,因此可以使用catch
進行異常捕獲。- 當接收到一個代表錯誤的 HTTP 狀態碼時,
fetch
返回的Promise
不會reject
,即使響應的 HTTP 狀態碼是 404 或 500。相反,它會resolve
,但會將 resolve 返回值的ok
屬性設置為false
,僅當網絡故障或請求被阻止時,才會reject
。這是請求為 404 的示例。
iframe 異常捕獲
error
事件可以捕獲到一些標簽的 src
加載異常,但 iframe 的情況有些不一樣。在網上找了一些資料,嘗試了下面的一些方法:
- 使用
window.onerror
方式,無法捕獲,這是測試頁面。 - 使用
window.frames[0].onerror
方式,無法捕獲,這是測試頁面。 - 在標簽上綁定
onerror
事件,沒有觸發,無法捕獲,這是測試頁面。 - 綁定
onload
事件,可以觸發,但無法直接的捕獲,例如一般的網站都設置了 404 頁面,當 src 加載的一個網頁找不到時,就會默認使用 404 網頁,雖然觸發了onload
事件,但仍然不知道是不是異常。這個時候,可以通過間接的檢測這個顯示 404 頁面的一些信息,來判斷是否異常,比如當發現這個頁面title
里面有 404 或者not found
之類的詞匯,那就說明 iframe 加載異常。這個根據實際情況,可以設置其它檢測的標識。這是測試頁面。
還有一種思路就是用一個異步請求,讓服務器端判斷一下是否能夠正常的訪問 src
的資源,如果請求返回正常,那就直接動態設置 src
的值,否則就是異常情況,直接上報即可。
跨域
跨域的出現是由於瀏覽器的同源策略,一旦發生,會在控制台出現很明顯的提示。通過查找資料發現,更多的方案是提前解決這個問題,但也有針對特定的情況進行捕獲的方法。下面是相關的資料:
- Is it possible to trap CORS errors?
- How to Detect Cross Origin (CORS) Error vs. Other Types of Errors for XMLHttpRequest() in Javascript
- Catching 'Origin is not allowed by Access-Control-Allow-Origin' error
- Is there anyway to catch sandbox Uncaught/Unsafe error messages from cross-origin scripts
- 解決 "Script Error" 的另類思路