JavaScript 網頁異常捕獲


JavaScript 網頁異常捕獲

一、異常大概分類


一般我們想要捕獲的異常大概分類:

1、語法錯誤

開發階段通過 IDE 提示和 eslint 等工具處理

注意:

a、onerror 事件代碼塊與 語法錯誤代碼塊 不在一起

b、或者同在一個代碼塊,但是 語法錯誤代碼塊 異步執行

都可以用 onerror 捕獲語法錯誤

setTimeout(() => {
   eval('function()') 
}, 1000);
// Uncaught SyntaxError: Function statements require a function name

2、引用錯誤,類型錯誤,uri 錯誤,范圍錯誤等等

非 try catch 包裹情況下

可以使用 onerror 捕獲同步錯誤、異步錯誤

console.log(a)
// Uncaught ReferenceError: a is not defined

Array.test() // 調用了 Array 上不存在的 test,值為 undefined,作為函數執行,則會拋出類型錯誤
// Uncaught TypeError: Array.test is not a function

new Array(12221312312)
// Uncaught RangeError: Invalid array length

decodeURI('%')
// Uncaught URIError: URI malformed

3、try{} catch{}

若 try 代碼塊報錯,只能在 catch 中捕獲

但是 try 代碼塊中若有異步錯誤代碼,catch 無法捕獲,會被 onerror 捕獲

try {
    setTimeout(() => {
        console.log('a', a) // 可以被 onerror 捕獲
    }, 1000)   
} catch(e) {
    console.log('e', e)
}
// Uncaught ReferenceError: a is not defined

4、Promise 拋出錯誤

a、沒有設置 catch 捕獲

let p = new Promise((resolve, reject) => {
    reject(1)
}) // 這里沒有做 catch 處理
// Uncaught (in promise) 1

b、在 catch 中報錯沒有捕獲

;(async function xx() {
    try {
        throw 1
    } catch(e) {
        console.log('a', a)  // 這里出錯可以使用 unhandledrejection 來捕獲
    }
})()
// Uncaught ReferenceError: a is not defined

上面兩種情況可以監聽 unhandledrejection 捕獲錯誤

window.addEventListener('unhandledrejection', function(e) {
    // e.preventDefault(); // 阻止異常向上拋出
    console.log('捕獲到異常 unhandledrejection :', e)
})

5、靜態資源加載失敗

a、在資源上添加 onerror 事件

// html
// <img src="" alt="" id="imgID">

// js
let imgID = document.getElementById('imgID')
imgID.onerror  = function(e) {
    console.log('img load error :>> ', e);
}
// 注意:onerror 需要定義之后,再設置圖片路徑,才能捕獲到加載失敗
imgID.src = 'http://xxx.png'

b、靜態資源網絡請求失敗事件不會冒泡,需要在捕獲階段捕獲

chrome、FF 中可以通過冒泡方式監聽 error 事件捕獲資源加載失敗

// 注意:此處會與上面的 onerror 事件一起觸發,將導致日志重復上報
// 可以只使用 addEventListener 捕獲模式統一監聽, 就不需要注冊 window.onerror 了
window.addEventListener(
    'error',
    error => {
        console.log('addEventListener 捕獲到異常:', error)
    },
    true
)

6、網頁崩潰

a、網頁加載后,埋入一個標志,表示正在加載

// 初次進來,將埋入一個標志,值為 pending ,正常退出后,會設置為 true
// 若網頁崩潰,第二次回來頁面后,讀取當前標志,如果值存在且為 true 則表示正常退出
// 如果不是 true ,則表示上次可能是崩潰了,需要上報之前定時更新的時間值
if(localStorage.getItem('good_exit') &&
    localStorage.getItem('good_exit') !== 'true') {
    // 日志上報,將崩潰之前的時間一起上報 localStorage.getItem('time_before_crash')
}
window.addEventListener('load', function () {
    localStorage.setItem('good_exit', 'pending');
    // 定時更新崩潰之前的網頁時間
    setInterval(function () {
        localStorage.setItem('time_before_crash', new Date().toString());
    }, 10000);
})

window.addEventListener('beforeunload', function () {
    // 網頁正常退出后,將埋入標志,設置成 true,表示正常退出
    localStorage.setItem('good_exit', 'true');
})

上面是在 第二次 進入頁面才知道網頁崩潰,那么有什么方法可以在網頁崩潰之后就可以上報呢

b、可以使用 Service Worker 來進行監控

其生命周期與頁面無關(關聯頁面未關閉時,它也可以退出,沒有關聯頁面時,它也可以啟動)

流程:

1、注冊 service worker js
2、
每間隔 10 秒,就向 worker 發送消息
消息中包含:
{
    type 字段:active 表示正常活躍,exit 表示正常退出
    time 字段:表示當前時間
}

在頁面要退出后,發送 type: exit,表示正常退出
3、
worker 內部注冊有消息接收事件,接收頁面發送過來的消息
接收到 type 為 active,表示正常活躍,更新內部 time 的值
接收到 type 為 exit,表示正常退出,更新內部 time 值為 0
worker 內部維護一個狀態對象,包含時間,值為頁面發送過來
每隔 15 秒檢查一次,若時間與上一次沒有改變,則說明頁面可能崩潰(注意區分 time 為 0 的情況)

7、Script Error

跨域腳本的錯誤信息,因為處於保護信息的原因,只會展示 Script Error

通過以下方式解決:

1、外鏈腳本增加 crossorigin 屬性:<script crossorigin src="http://other-domain.js"></script> 
2、同時腳本的 Access-Control-Allow-Origin: 設置為 * 或者 當前域名

8、iframe Error

<iframe src="./test.html"></iframe>
<script>
window.frames[0].onerror = function(msg, source, lineno, colno, error) {
    console.log('frames onerror :>> ', msg,source,lineno,colno,error)
}
</script>

9、vue 自身 try catch 處理了錯誤,導致我們無法捕獲

使用 Vue 提供的 errorHandler 方法捕獲

Vue.config.errorHandler = function(err, vm, info) {
    let {
        message, // 異常信息
        name, // 異常名稱
        script, // 異常腳本url
        line, // 異常行號
        column, // 異常列號
        stack // 異常堆棧信息
    } = err
    // vm 為拋出異常的 Vue 實例
    // info 為 Vue 特定的錯誤信息,比如錯誤所在的生命周期鈎子
    console.log('vue err,vm,info :>> ', err, vm, info)
}

二、捕獲實踐

所以總結下來:

// 1、try catch 中的 catch 錯誤捕獲
可以在 webpack 打包時候,使用 AST 方式解析並在 catch 中插入日志上報代碼

// 2、error 事件
window.addEventListener(
    'error',
    error => {
        let data = {}
        // 此處與上面的 onerror 會重復事件
        let { colno, lineno, message, filename, error, stack } = error
        //不一定所有瀏覽器都支持 colno 參數
        let col = colno || (window.event && window.event.errorCharacter) || 0
 
        data.url = url
        data.line = line
        data.col = col

        if (!!stack){
            //如果瀏覽器有堆棧信息
            //直接使用
            data.msg = stack.toString()
        }else if (!!arguments.callee){
            //嘗試通過callee拿堆棧信息
            let ext = []
            let f = arguments.callee.caller, c = 3
            //這里只拿三層堆棧信息
            while (f && (--c>0)) {
               ext.push(f.toString())
               if (f  === f.caller) {
                    break //如果有環
               }
               f = f.caller
            }
            ext = ext.join(",")
            data.msg = ext
        }
        console.log('3、addEventListener error 捕獲階段>>>異常:', data)
    },
    true
)

// 3、unhandledrejection
window.addEventListener('unhandledrejection', function(e) {
    // e.preventDefault(); // 阻止異常向上拋出
    console.log('4、promise 異常 unhandledrejection :', e)
})

// 4、errorHandler
Vue.config.errorHandler = function(err, vm, info) {
    // vm 為拋出異常的 Vue 實例
    // info 為 Vue 特定的錯誤信息,比如錯誤所在的生命周期鈎子
    let {
        message, // 異常信息
        name, // 異常名稱
        script, // 異常腳本url
        line, // 異常行號
        column, // 異常列號
        stack // 異常堆棧信息
    } = err
    console.log('2、vue errorHandler :>> ', err, vm, info)
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM