前端異常監(jiān)控和容災(zāi)
點(diǎn)擊上方 程序員成長(zhǎng)指北,關(guān)注公眾號(hào)
回復(fù)1,加入高級(jí)Node交流群
異常就是程序出現(xiàn)了意料之外的情況,影響了程序最終的呈現(xiàn)結(jié)果。所以我們開(kāi)發(fā)的時(shí)候就非常有必要未雨綢繆,進(jìn)行異常監(jiān)控,以應(yīng)對(duì)突如其來(lái)的問(wèn)題.
既可以增強(qiáng)用戶體驗(yàn),我們開(kāi)發(fā)者也能遠(yuǎn)程定位問(wèn)題,尤其是移動(dòng)端 盡管對(duì) JS 而言,異常一般只會(huì)使當(dāng)前執(zhí)行的任務(wù)中止,基本不會(huì)導(dǎo)致崩潰.
可異常監(jiān)控卻是一個(gè)完善的前端方案必須具備的.
接下來(lái)就針對(duì)我們前端,需要做的異常一一說(shuō)明
異常監(jiān)控
JS 執(zhí)行異常
使用 try-catch的話捕捉不到具體語(yǔ)法錯(cuò)誤和異步錯(cuò)誤,所以推薦用在可預(yù)見(jiàn)情況下的錯(cuò)誤監(jiān)控使用 window.onerror,比try-catch強(qiáng),不過(guò)也捕獲不到資源加載異常或者接口異常,推薦用來(lái)捕獲預(yù)料之外的錯(cuò)誤
兩者結(jié)合更好
收集到的錯(cuò)誤信息打印出來(lái)是這樣子的

window.onerror = function (msg, url, row, col, error) {
console.table({ msg, url, row, col, error: error.stack })
let errorMsg = {
type: 'javascript',
// msg錯(cuò)誤消息,error是錯(cuò)誤對(duì)象,這里拿的是error.stack(異常信息)
msg: error && error.stack ? error.stack || msg,
// 發(fā)生錯(cuò)誤的行數(shù)
row,
// 列數(shù),也就是第幾個(gè)字符
col,
// 發(fā)生錯(cuò)誤的頁(yè)面地址
url,
// 發(fā)生錯(cuò)誤的時(shí)間
time: Date.now()
}
// 然后可以把這個(gè) errorMsg 存到一個(gè)數(shù)組里,統(tǒng)一上報(bào)
// 也可以直接上報(bào)
Axios.post({ 'https://xxxx', errorMsg })
// 如果return true,錯(cuò)誤就不會(huì)拋到控制臺(tái)
}
上報(bào)有兩種方式,一種是如上面代碼中的用 AJAX,會(huì)有跨域所以需要服務(wù)端支持;還有一種是用 Image 對(duì)象,這有一個(gè)好處就是圖片請(qǐng)求沒(méi)有跨域;注意URL長(zhǎng)度不要超過(guò)限制就行。后面的例子中就不一一列舉了
let url = 'https://xxx' + '錯(cuò)誤信息'
new Image.src = url
資源加載異常
使用 addEventListener('error', callback, true) 在捕獲階段捕捉資源加載錯(cuò)誤信息,然后上報(bào)服務(wù)器
使用 addEventListener('error', callback, true) 在捕獲階段捕捉資源加載錯(cuò)誤信息,然后上報(bào)服務(wù)器
Promise 異常
unhandledrejection
使用 addeventListener('unhandledrejection',callback)捕獲 Promise 錯(cuò)誤。不過(guò)捕捉不到行數(shù),觸發(fā)時(shí)間在被 reject 但沒(méi)有 reject 處理的時(shí)候,可能發(fā)生在 window 下,也可能在 Worker 中
window.addEventListener("unhandledrejection", (e) => {
console.log(e)
let errorMsg = {
type: 'promise',
msg: e.reason.stack || e.reason
// .....
}
Axios.post({ 'https://xxxx', errorMsg })
// 如果return true,錯(cuò)誤就不會(huì)拋到控制臺(tái)
})
new Promise(() => {
s
})
打印出來(lái)是這么個(gè)東西

rejectionhandled
Promise 錯(cuò)誤已被處理會(huì)觸發(fā)這個(gè)
window.addEventListener("unhandledrejection", (e) => {
console.log('錯(cuò)誤了')
})
window.addEventListener("rejectionhandled", (e) => {
console.log('錯(cuò)誤已經(jīng)處理了')
})
Vue 異常
errorHandle
Vue為組件呈現(xiàn)函數(shù)和監(jiān)視程序期間沒(méi)有捕獲的錯(cuò)誤分配的一個(gè)處理程序。不過(guò)這個(gè)方法一旦捕獲取錯(cuò)誤后,錯(cuò)誤就不會(huì)拋到控制臺(tái)
Vue.config.errorHandler = (err, vm, info) => {
// err 錯(cuò)誤處理
// vm vue實(shí)例
// info 是特定于vue的錯(cuò)誤信息,比如哪個(gè)生命周期勾子
// 如果需要把錯(cuò)誤拋到控制臺(tái),需要在這里加上這一行
console.error(err)
}
warnHandle
是Vue警告分配一個(gè)自定義處理程序。不過(guò)只在開(kāi)發(fā)環(huán)境有效,生產(chǎn)環(huán)境會(huì)被自忽略
Vue.config.warnHandle = (msg, vm, trace){
// trace 是組件層次結(jié)構(gòu)
}
renderError
默認(rèn)的渲染函數(shù)遇到錯(cuò)誤時(shí),提供了一個(gè)代替渲染輸出的。這個(gè)和熱重新加載一起用會(huì)很棒
new Vue({
render (h){
throw new Error('oops')
},
renderError (h, err){
return h('per',{ style: { color: red } }, err.stack)
}
}).$mount('#app')
errorCaptured
任何派生組件捕獲錯(cuò)誤時(shí)調(diào)用。它可以 return false 來(lái)阻止錯(cuò)誤傳播。可以在這個(gè)勾子里修改組件狀態(tài)。不過(guò)如果是在模板或呈現(xiàn)函數(shù)里有條件語(yǔ)句,在捕獲到錯(cuò)誤時(shí),這些條件語(yǔ)句會(huì)短路,可能進(jìn)入一個(gè)無(wú)限渲染循環(huán)
Vue.component('ErrorBoundary',{
data: () => { ... }
errorCaptured(err, vm, info){
// err 錯(cuò)誤信息
// vm 觸發(fā)錯(cuò)誤的組件實(shí)例
// info 錯(cuò)誤捕獲位置信息
return false
}
})
React 異常
getDerivedStateFromError
React 也有自帶的捕獲所有子組件中錯(cuò)誤的方法,這個(gè)生命周期會(huì)在后代組件拋出錯(cuò)誤時(shí)被調(diào)用。注意這個(gè)是在渲染階段調(diào)用的,所以不允許出現(xiàn)副作用
class ErrorBoundary extends React.Component {
constructor(props) {
super(props)
this.state = { hasError: false }
}
static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染可以顯降級(jí) UI
return { hasError: true }
}
}
componentDidCatch
這個(gè)生命周期也會(huì)在后代組件拋出錯(cuò)誤時(shí)被調(diào)用,但是不會(huì)捕獲事件處理器和異步代碼的異常。它會(huì)在【提交】階段被調(diào)用,所以允許出現(xiàn)副作用
class ErrorBoundary extends React.Component {
constructor(props) {
super(props)
}
componentDidCatch(error, info){
// error 錯(cuò)誤信息
// info.componentStack 錯(cuò)誤組件位置
}
}
說(shuō)了前端可能發(fā)生的各種異常處理,那么后端異常呢?前端容災(zāi)就是
前端容災(zāi)
前端容災(zāi)指的因?yàn)楦鞣N原因后端接口掛了(比如服務(wù)器斷電斷網(wǎng)等等),前端依然能保證頁(yè)面信息能完整展示。
比如 banner 或者列表之類(lèi)的等等數(shù)據(jù)是從接口獲取的,要是接口獲取不到了,怎么辦呢?
LocalStorage
首先,使用 LocalStorage
在接口正常返回的時(shí)候把數(shù)據(jù)都存到 LocalStorage ,可以把接口路徑作為 key,返回的數(shù)據(jù)作為 value。
然后之次再請(qǐng)求,只要請(qǐng)求失敗,就讀取 LocalStorage,把上次的數(shù)據(jù)拿出來(lái)展示,并上報(bào)錯(cuò)誤信息,以獲得緩沖時(shí)間
CDN
同時(shí),每次更新都要備份一份靜態(tài)數(shù)據(jù)放到CDN
在接口請(qǐng)求失敗的時(shí)候,并且 LocalStorage 也沒(méi)有數(shù)據(jù)的情況下,就去 CDN 摘取備份的靜態(tài)數(shù)據(jù)
Service Worker
假如不只是接口數(shù)據(jù),整個(gè) html 都想存起來(lái),就可以使用 Service Worker 做離線存儲(chǔ)
利用 Service Worker 的請(qǐng)求攔截,不管是存接口數(shù)據(jù),還是存頁(yè)面靜態(tài)資源文件都可以
// 攔截所有請(qǐng)求事件 緩存中有請(qǐng)求的數(shù)據(jù)就直接用緩存,否則去請(qǐng)求數(shù)據(jù)
self.addEventListener('fetch', e => {
// 查找request中被緩存命中的response
e.respondWith(caches.match(e.request).then( response => {
if (response) {
return response
}
console.log('fetch source')
}))
})
做好這些,整個(gè)網(wǎng)站就完全可以離線運(yùn)行了
我組建了一個(gè)氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對(duì)Node.js學(xué)習(xí)感興趣的話(后續(xù)有計(jì)劃也可以),我們可以一起進(jìn)行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。

“分享、點(diǎn)贊、在看” 支持一波 
