Vue實現(xiàn)全局異常處理的幾種方案
在開發(fā)組件庫或者插件,經(jīng)常會需要進(jìn)行全局異常處理,從而實現(xiàn):\
- 全局統(tǒng)一處理異常;
- 為開發(fā)者提示錯誤信息;
- 方案降級處理等等。
那么如何實現(xiàn)上面功能呢?本文先簡單實現(xiàn)一個異常處理方法,然后結(jié)合 Vue3 源碼中的實現(xiàn)詳細(xì)介紹,最后總結(jié)實現(xiàn)異常處理的幾個核心。
本文 Vue3 版本為 3.0.11
一、前端常見異常
對于前端來說,常見的異常比較多,比如:
- JS 語法異常;
- Ajax 請求異常;
- 靜態(tài)資源加載異常;
- Promise 異常;
- iframe 異常;
等等
最常用的比如:\
- window.onerror
通過 window.onerror文檔[2]可知,當(dāng) JS 運(yùn)行時發(fā)生錯誤(包括語法錯誤),觸發(fā) window.onerror():
window.onerror = function(message, source, lineno, colno, error) {
console.log('捕獲到異常:',{message, source, lineno, colno, error});
}函數(shù)參數(shù):
- message:錯誤信息(字符串)??捎糜贖TML onerror=""處理程序中的 event。
- source:發(fā)生錯誤的腳本URL(字符串)
- lineno:發(fā)生錯誤的行號(數(shù)字)
- colno:發(fā)生錯誤的列號(數(shù)字)
- error:Error對象(對象)
若該函數(shù)返回true,則阻止執(zhí)行默認(rèn)事件處理函數(shù)。
- try...catch 異常處理
另外,我們也經(jīng)常會使用 try...catch 語句處理異常:
try {
// do something
} catch (error) {
console.error(error);
}更多處理方式,可以閱讀前面推薦的文章。
- 思考
大家可以思考下,自己在業(yè)務(wù)開發(fā)過程中,是否也是經(jīng)常要處理這些錯誤情況?那么像 Vue3 這樣復(fù)雜的庫,是否也是到處通過 try...catch來處理異常呢?接下來一起看看。
二、實現(xiàn)簡單的全局異常處理
在開發(fā)插件或庫時,我們可以通過 try...catch封裝一個全局異常處理方法,將需要執(zhí)行的方法作為參數(shù)傳入,調(diào)用方只要關(guān)心調(diào)用結(jié)果,而無需知道該全局異常處理方法內(nèi)部邏輯。大致使用方法如下:
const errorHandling = (fn, args) => {
let result;
try{
result = args ? fn(...args) : fn();
} catch (error){
console.error(error)
}
return result;
}測試一下:
const f1 = () => {
console.log('[f1 running]')
throw new Error('[f1 error!]')
}
errorHandling(f1);
/*
輸出:
[f1 running]
Error: [f1 error!]
at f1 (/Users/wangpingan/leo/www/node/www/a.js:14:11)
at errorHandling (/Users/wangpingan/leo/www/node/www/a.js:4:39)
at Object.<anonymous> (/Users/wangpingan/leo/www/node/www/a.js:17:1)
at Module._compile (node:internal/modules/cjs/loader:1095:14)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1147:10)
at Module.load (node:internal/modules/cjs/loader:975:32)
at Function.Module._load (node:internal/modules/cjs/loader:822:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
at node:internal/main/run_main_module:17:47
*/可以看到,當(dāng)需要為方法做異常處理時,只要將該方法作為參數(shù)傳入即可。但是上面示例跟實際業(yè)務(wù)開發(fā)的邏輯差得有點多,實際業(yè)務(wù)中,我們經(jīng)常會遇到方法的嵌套調(diào)用,那么我們試一下:
const f1 = () => {
console.log('[f1]')
f2();
}
const f2 = () => {
console.log('[f2]')
f3();
}
const f3 = () => {
console.log('[f3]')
throw new Error('[f3 error!]')
}
errorHandling(f1)
/*
輸出:
[f1 running]
[f2 running]
[f3 running]
Error: [f3 error!]
at f3 (/Users/wangpingan/leo/www/node/www/a.js:24:11)
at f2 (/Users/wangpingan/leo/www/node/www/a.js:19:5)
at f1 (/Users/wangpingan/leo/www/node/www/a.js:14:5)
at errorHandling (/Users/wangpingan/leo/www/node/www/a.js:4:39)
at Object.<anonymous> (/Users/wangpingan/leo/www/node/www/a.js:27:1)
at Module._compile (node:internal/modules/cjs/loader:1095:14)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1147:10)
at Module.load (node:internal/modules/cjs/loader:975:32)
at Function.Module._load (node:internal/modules/cjs/loader:822:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
*/這樣也是沒問題的。那么接下來就是在 errorHandling方法的 catch分支實現(xiàn)對應(yīng)異常處理即可。接下來看看 Vue3 源碼中是如何處理的?
三、Vue3 如何實現(xiàn)異常處理
理解完上面示例,接下來看看在 Vue3 源碼中是如何實現(xiàn)異常處理的,其實現(xiàn)起來也是很簡單。
- 實現(xiàn)異常處理方法
在 errorHandling.ts 文件中定義了 callWithErrorHandling和 callWithAsyncErrorHandling兩個處理全局異常的方法。顧名思義,這兩個方法分別處理:
- callWithErrorHandling:處理同步方法的異常;
- callWithAsyncErrorHandling:處理異步方法的異常。
使用方式如下:
callWithAsyncErrorHandling( handler, instance, ErrorCodes.COMPONENT_EVENT_HANDLER, args )
代碼實現(xiàn)大致如下:
// packages/runtime-core/src/errorHandling.ts
// 處理同步方法的異常
export function callWithErrorHandling(
fn: Function,
instance: ComponentInternalInstance | null,
type: ErrorTypes,
args?: unknown[]
) {
let res
try {
res = args ? fn(...args) : fn(); // 調(diào)用原方法
} catch (err) {
handleError(err, instance, type)
}
return res
}
// 處理異步方法的異常
export function callWithAsyncErrorHandling(
fn: Function | Function[],
instance: ComponentInternalInstance | null,
type: ErrorTypes,
args?: unknown[]
): any[] {
// 省略其他代碼
const res = callWithErrorHandling(fn, instance, type, args)
if (res && isPromise(res)) {
res.catch(err => {
handleError(err, instance, type)
})
}
// 省略其他代碼
}callWithErrorHandling方法處理的邏輯比較簡單,通過簡單的 try...catch 做一層封裝。而 callWithAsyncErrorHandling 方法就比較巧妙,通過將需要執(zhí)行的方法傳入 callWithErrorHandling方法處理,并將其結(jié)果通過 .catch方法進(jìn)行處理。
- 處理異常
在上面代碼中,遇到報錯的情況,都會通過 handleError()處理異常。其實現(xiàn)大致如下:
// packages/runtime-core/src/errorHandling.ts
// 異常處理方法
export function handleError(
err: unknown,
instance: ComponentInternalInstance | null,
type: ErrorTypes,
throwInDev = true
) {
// 省略其他代碼
logError(err, type, contextVNode, throwInDev)
}
function logError(
err: unknown,
type: ErrorTypes,
contextVNode: VNode | null,
throwInDev = true
) {
// 省略其他代碼
console.error(err)
}保留核心處理邏輯之后,可以看到這邊處理也是相當(dāng)簡單,直接通過console.error(err)輸出錯誤內(nèi)容。
- 配置 errorHandler 自定義異常處理函數(shù)
在使用 Vue3 時,也支持「指定自定義異常處理函數(shù)」,來處理「組件渲染函數(shù)」和「偵聽器執(zhí)行期間」拋出的未捕獲錯誤。這個處理函數(shù)被調(diào)用時,可獲取錯誤信息和相應(yīng)的應(yīng)用實例。文檔參考:《errorHandler》 使用方法如下,在項目 main.js文件中配置:
// src/main.js
app.config.errorHandler = (err, vm, info) => {
// 處理錯誤
// `info` 是 Vue 特定的錯誤信息,比如錯誤所在的生命周期鉤子
}那么 errorHandler()是何時執(zhí)行的呢?我們繼續(xù)看看源碼中 handleError() 的內(nèi)容,可以發(fā)現(xiàn):
// packages/runtime-core/src/errorHandling.ts
export function handleError(
err: unknown,
instance: ComponentInternalInstance | null,
type: ErrorTypes,
throwInDev = true
) {
const contextVNode = instance ? instance.vnode : null
if (instance) {
// 省略其他代碼
// 讀取 errorHandler 配置項
const appErrorHandler = instance.appContext.config.errorHandler
if (appErrorHandler) {
callWithErrorHandling(
appErrorHandler,
null,
ErrorCodes.APP_ERROR_HANDLER,
[err, exposedInstance, errorInfo]
)
return
}
}
logError(err, type, contextVNode, throwInDev)
}通過 instance.appContext.config.errorHandler取到全局配置的自定義錯誤處理函數(shù),存在時則執(zhí)行,當(dāng)然,這邊也是通過前面定義的 callWithErrorHandling來調(diào)用。
- 調(diào)用 errorCaptured 生命周期鉤子
在使用 Vue3 的時候,也可以通過 errorCaptured生命周期鉤子來「捕獲來自后代組件的錯誤」。文檔參考:《errorCaptured》入?yún)⑷缦拢?/p>
(err: Error, instance: Component, info: string) => ?boolean
此鉤子會收到三個參數(shù):錯誤對象、發(fā)生錯誤的組件實例以及一個包含錯誤來源信息的字符串。此鉤子可以返回 false以「阻止該錯誤繼續(xù)向上傳播?!褂信d趣的同學(xué)可以通過文檔,「查看具體的錯誤傳播規(guī)則」。使用方法如下,父組件監(jiān)聽 onErrorCaptured生命周期(示例代碼使用 Vue3 setup 語法):
<template>
<Message></Message>
</template>
<script setup>
// App.vue
import { onErrorCaptured } from 'vue';
import Message from './components/Message.vue'
onErrorCaptured(function(err, instance, info){
console.log('[errorCaptured]', err, instance, info)
})
</script>子組件如下:
<template>
<button @click="sendMessage">發(fā)送消息</button>
</template>
<script setup>
// Message.vue
const sendMessage = () => {
throw new Error('[test onErrorCaptured]')
}
</script>當(dāng)點擊「發(fā)送消息」按鈕,控制臺便輸出錯誤:
[errorCaptured] Error: [test onErrorCaptured]
at Proxy.sendMessage (Message.vue:36:15)
at _createElementVNode.onClick._cache.<computed>._cache.<computed> (Message.vue:3:39)
at callWithErrorHandling (runtime-core.esm-bundler.js:6706:22)
at callWithAsyncErrorHandling (runtime-core.esm-bundler.js:6715:21)
at HTMLButtonElement.invoker (runtime-dom.esm-bundler.js:350:13) Proxy {sendMessage: ƒ, …} native event handler
可以看到 onErrorCaptured生命周期鉤子正常執(zhí)行,并輸出子組件 Message.vue內(nèi)的異常。
那么這個又是如何實現(xiàn)呢?還是看 errorHandling.ts 中的 handleError() 方法:
// packages/runtime-core/src/errorHandling.ts
export function handleError(
err: unknown,
instance: ComponentInternalInstance | null,
type: ErrorTypes,
throwInDev = true
) {
const contextVNode = instance ? instance.vnode : null
if (instance) {
let cur = instance.parent
// the exposed instance is the render proxy to keep it consistent with 2.x
const exposedInstance = instance.proxy
// in production the hook receives only the error code
const errorInfo = __DEV__ ? ErrorTypeStrings[type] : type
while (cur) {
const errorCapturedHooks = cur.ec // ①取出組件配置的 errorCaptured 生命周期方法
if (errorCapturedHooks) {
// ②循環(huán)執(zhí)行 errorCaptured 中的每個 Hook
for (let i = 0; i < errorCapturedHooks.length; i++) {
if (
errorCapturedHooks[i](err, exposedInstance, errorInfo "i") === false
) {
return
}
}
}
cur = cur.parent
}
// 省略其他代碼
}
logError(err, type, contextVNode, throwInDev)
}這邊會先獲取 instance.parent作為當(dāng)前處理的組件實例進(jìn)行遞歸,每次將取出組件配置的 errorCaptured 生命周期方法的數(shù)組并循環(huán)調(diào)用其每一個鉤子,然后再取出當(dāng)前組件的父組件作為參數(shù),最后繼續(xù)遞歸調(diào)用下去。
- 實現(xiàn)錯誤碼和錯誤消息
Vue3 還為異常定義了錯誤碼和錯誤信息,在不同的錯誤情況有不同的錯誤碼和錯誤信息,讓我們能很方便定位到發(fā)生異常的地方。錯誤碼和錯誤信息如下:
// packages/runtime-core/src/errorHandling.ts
export const enum ErrorCodes {
SETUP_FUNCTION,
RENDER_FUNCTION,
WATCH_GETTER,
WATCH_CALLBACK,
// ... 省略其他
}
export const ErrorTypeStrings: Record<number | string, string> = {
// 省略其他
[LifecycleHooks.RENDER_TRACKED]: 'renderTracked hook',
[LifecycleHooks.RENDER_TRIGGERED]: 'renderTriggered hook',
[ErrorCodes.SETUP_FUNCTION]: 'setup function',
[ErrorCodes.RENDER_FUNCTION]: 'render function',
// 省略其他
[ErrorCodes.SCHEDULER]:
'scheduler flush. This is likely a Vue internals bug. ' +
'Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/vue-next'
}當(dāng)不同錯誤情況,根據(jù)錯誤碼 ErrorCodes來獲取 ErrorTypeStrings錯誤信息進(jìn)行提示:
// packages/runtime-core/src/errorHandling.ts
function logError(
err: unknown,
type: ErrorTypes,
contextVNode: VNode | null,
throwInDev = true
) {
if (__DEV__) {
const info = ErrorTypeStrings[type]
warn(`Unhandled error${info ? ` during execution of ${info}` : ``}`)
// 省略其他
} else {
console.error(err)
}
}- 實現(xiàn) Tree Shaking
關(guān)于 Vue3 實現(xiàn) Tree Shaking 的介紹,可以看我之前寫的高效實現(xiàn)框架和 JS 庫瘦身[7]。其中,logError 方法中就使用到了:
// packages/runtime-core/src/errorHandling.ts
function logError(
err: unknown,
type: ErrorTypes,
contextVNode: VNode | null,
throwInDev = true
) {
if (__DEV__) {
// 省略其他
} else {
console.error(err)
}
}當(dāng)編譯成 production 環(huán)境后,__DEV__分支的代碼不會被打包進(jìn)去,從而優(yōu)化包的體積。
四、總結(jié)
到上面一部分,我們就差不多搞清楚 Vue3 中全局異常處理的核心邏輯了。我們在開發(fā)自己的錯誤處理方法時,也可以考慮這幾個核心點:
- 支持同步和異步的異常處理;
- 設(shè)置業(yè)務(wù)錯誤碼、業(yè)務(wù)錯誤信息;
- 支持自定義錯誤處理方法;
- 支持開發(fā)環(huán)境錯誤提示;
- 支持 Tree Shaking。
這幾點在你設(shè)計插件的時候,都可以考慮進(jìn)去的~
到此這篇關(guān)于Vue實現(xiàn)全局異常處理的幾種方案的文章就介紹到這了,更多相關(guān)Vue 全局異常內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue中使用better-scroll實現(xiàn)輪播圖組件
better-scroll 是一款重點解決移動端(已支持 PC)各種滾動場景需求的插件。這篇文章主要介紹了Vue中使用better-scroll實現(xiàn)輪播圖組件的實例代碼,需要的朋友可以參考下2020-03-03
Vue3-新特性defineOptions和defineModel示例詳解
在Vue3.3中新引入了defineOptions宏主要是用來定義Option API的選項,可以用defineOptions定義任意的選項,props、emits、expose、slots除外,本文給大家介紹Vue3-新特性defineOptions和defineModel,感興趣的朋友一起看看吧2023-11-11
vue項目中頁面底部出現(xiàn)白邊及空白區(qū)域錯誤的問題
這篇文章主要介紹了vue項目中頁面底部出現(xiàn)白邊及空白區(qū)域錯誤的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-08-08
Object.assign觸發(fā)watch原理示例解析
這篇文章主要為大家介紹了Object.assign觸發(fā)watch原理示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11

