vue3中的错误处理方案
# 概述
在设计一个框架时,如何处理错误是必不可少的环节。错误在程序开发中避无可避,那么出现错误时,至少要保证程序不会崩溃。合理的错误处理方案会大大增强框架的健壮性,覆盖异常场景,提高系统的可用性。
本文将探讨vue3
中的错误处理设计。
# 错误处理设计
vue3
中的错误处理逻辑集中在程序运行时,源码部分位于runtime-core\src\errorHandling.ts
。其内部封装了两个工具函数:callWithErrorHandling
和callWithAsyncErrorHandling
,用于处理函数调用中的错误,它们在vue3
的响应式系统和组件生命周期中扮演着重要角色,通常情况下只是vue3
内部调用。
# ErrorCodes
和ErrorTypeStrings
类型
vue3
的错误处理定义了错误码和错误类型,用来标识错误,方便排查问题。
# callWithErrorHandling
方法
callWithErrorHandling
函数用于同步函数调用时捕获错误,会在函数执行期间捕获并处理可能出现的异常情况或同步错误
其实现如下:
function callWithErrorHandling(fn, instance, type, args) {
try {
return args ? fn(...args) : fn();
} catch (err) {
handleError(err, instance, type);
}
}
2
3
4
5
6
7
上述示例很好理解,就是用到try
、catch
取执行fn
函数,并返回fn
函数执行的结果;在catch
中捕获错误,调用handleError
函数
# callWithAsyncErrorHandling
方法
callWithAsyncErrorHandling
函数用于处理异步函数调用中的错误,它能够捕捉和处理Promise
的reject
错误。
其实现如下
function callWithAsyncErrorHandling(fn, instance, type, args) {
//fn:函数 instance:vue实例 type:类型 args:传递给fn的参数,下同
if (isFunction(fn)) {
const res = callWithErrorHandling(fn, instance, type, args);
if (res && isPromise(res)) {
res.catch((err) => {
handleError(err, instance, type);
});
}
return res;
}
if (isArray(fn)) {
const values = [];
for (let i = 0; i < fn.length; i++) {
values.push(callWithAsyncErrorHandling(fn[i], instance, type, args));
}
return values;
} else if (true) {
warn2(
`Invalid value type passed to callWithAsyncErrorHandling(): ${typeof fn}`
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
上面这段代码也不难理解,首先判断fn
是否是函数,若是函数,则调用callWithAsyncErrorHandling
,然后判断其返回值是否是Promise
,若是Promise
,则在其catch
方法中绑定handleError
方法;若fn
则是数组,则遍历fn
,循环调用callWithAsyncErrorHandling
;如果fn
既不是函数又不是数组,则调用warn2
,抛出异常。
# handleError
方法
上述两个工具函数在捕获到错误后,都会调用handleError
处理错误,其作用就是找出错误的上下文,指明错误的地方,以及处理错误回调。
其实现如下:
function handleError(err, instance, type, throwInDev = true) {
const contextVNode = instance ? instance.vnode : null;
if (instance) {
let cur = instance.parent;
const exposedInstance = instance.proxy;
const errorInfo = true
? ErrorTypeStrings[type]
: `https://vuejs.org/error-reference/#runtime-${type}`;
while (cur) {
const errorCapturedHooks = cur.ec;
if (errorCapturedHooks) {
for (let i = 0; i < errorCapturedHooks.length; i++) {
if (
errorCapturedHooks[i](err, exposedInstance, errorInfo) === false
) {
return;
}
}
}
cur = cur.parent;
}
const appErrorHandler = instance.appContext.config.errorHandler;
if (appErrorHandler) {
pauseTracking();
callWithErrorHandling(appErrorHandler, null, 10 /* APP_ERROR_HANDLER */, [
err,
exposedInstance,
errorInfo,
]);
resetTracking();
return;
}
}
logError(err, type, contextVNode, throwInDev);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
handleError
方法接收四个参数err
, instance
, type
, throwInDev
。分别是错误信息、vue 实例、错误类型、是否在环境中抛出错误。默认都会抛出异常。若instance
存在,则判断当前节点的父节点,在父组件中去捕获并处理子组件的错误。下面会介绍onErrorCaptured
。
handleError
中还会判断全局变量上是否挂载了errorHandler
函数,如果系统的前端侧需要处理错误日志,监测前端错误的,就可以在main.js
这样写,
app.config.errorHandler = (p) => {
const [err, exposedInstance, errorInfo] = p; //对应handleError中的变量
};
2
3
# onErrorCaptured
cur.ec
实质上是获取组件的生命周期函数onErrorCaptured
,这个钩子函数主要用于捕获和处理其所有子组件的抛出的错误,以便在更高层级的组件中进行错误处理。
用法一般如下
errorCaptured(err, instance, info) {
// err:处理错误 info:包含关于错误的额外信息,如发生在哪个生命周期钩子
/*** 逻辑代码 */
return false; // true:错误会继续传播 false:可以阻止错误继续向上抛出
},
2
3
4
5
6
7