在开发组件库或插件时,往往需要进行全局的异常处理,从而实现:全局统一的异常处理;为开发者提示错误信息;解决降级处理等那么如何实现以上功能呢?本文先简单实现一个异常处理方法,然后结合Vue3源码中的实现进行详细介绍,最后总结出实现异常处理的几个核心。本文中Vue3版本为3.0.111.前端常见异常对于前端来说,常见的异常有很多,比如:JS语法异常;Ajax请求异常;静态资源加载异常;承诺异常;iframe异常;等这些异常如何处理处理可以看这两篇文章:《你不知道的前端异常处理》《如何优雅处理前端异常?》最常用的例子:1.window.onerror根据window.onerror文档,当while发生错误(包括语法错误)时JS正在运行,window.onerror()被触发:onerror=function(message,source,lineno,colno,error){console.log('Exceptioncaught:',{message,source,lineno,colno,error});}函数参数:message:错误信息(字符串)。可用于HTMLonerror=""处理程序中的事件。source:发生错误的脚本的URL(string)lineno:发生错误的行号(number)colno:发生错误的列号(number)error:错误对象(object)如果这个函数返回true,它会阻止默认事件处理函数的执行。2、try...catch异常处理另外,我们经常使用try...catch语句来处理异常:try{//dosomething}catch(error){console.error(error);}更多处理方式,你可以阅读之前推荐的文章。3、思考大家可以想一想,在业务开发过程中,是不是经常要处理这些错误情况?那么像Vue3这样复杂的库是不是也到处通过try...catch来处理异常呢?一起来看看吧。2.实现简单的全局异常处理在开发插件或库时,我们可以通过try...catch封装一个全局异常处理方法,将要执行的方法作为参数传入。调用者只需要关心调用结果而无需知道。全局异常处理方法的内部逻辑。一般使用方法如下:consterrorHandling=(fn,args)=>{letresult;尝试{结果=参数?fn(...args):fn();}catch(error){console.error(error)}returnresult;}测试它:constf1=()=>{console.log('[f1running]')thrownewError('[f1error!]')}errorHandling(f1);/*output:[f1running]Error:[f1error!]atf1(/Users/wangpingan/leo/www/node/www/a.js:14:11)aterrorHandling(/Users/wangpingan/leo/www/node/www/a.js:4:39)atObject.(/Users/wangpingan/leo/www/node/www/a.js:17:1)atModule._compile(node:internal/modules/cjs/loader:1095:14)在Object.Module._extensions..js(node:internal/modules/cjs/loader:1147:10)在Module.load(node:internal/模块/cjs/loader:975:32)在函数中。Module._load(node:internal/modules/cjs/loader:822:12)atFunction.executeUserEntryPoint[asrunMain](node:internal/modules/run_main:81:12)atnode:internal/main/run_main_module:17:47*/可以看到,当需要对某个方法做异常处理时,将方法作为参数传入即可能但是上面的例子和实际业务开发的逻辑有点出入。在实际业务中,我们经常会遇到方法的嵌套调用,那么我们试试:constf1=()=>{console.log('[f1]')f2();}constf2=()=>{console.log('[f2]')f3();}constf3=()=>{console.log('[f3]')thrownewError('[f3error!]')}errorHandling(f1)/*输出:[f1running][f2running][f3running]Error:[f3error!]atf3(/Users/wangpingan/leo/www/node/www/a.js:24:11)atf2(/Users/wangpingan/leo/www/node/www/a.js:19:5)在f1(/Users/wangpingan/leo/www/node/www/a.js:14:5)在errorHandling(/Users/wangpingan/leo/www/node/www/a.js:4:39)atObject.(/Users/wangpingan/leo/www/node/www/a.js:27:1)atModule._compile(node:internal/modules/cjs/loader:1095:14)在Object.Module._extensions..js(node:internal/modules/cjs/loader:1147:10)在Module.load(node:internal/modules/cjs/loader:975:32)在Function.Module._load(node:internal/modules/cjs/loader:822:12)在Function.executeUserEntryPoint[asrunMain](node:internal/modules/run_main:81:12)*/这个没问题,那么接下来就是在errorHandling方法的catch分支中实现相应的异常处理。接下来我们看看Vue3源码中是如何处理的?3、Vue3是如何实现异常处理的理解了上面的例子之后,我们再来看看Vue3源码中是如何实现异常处理的,实现起来也很简单。1.实现异常处理方法在errorHandling.ts文件中,定义了两个处理全局异常的方法callWithErrorHandling和callWithAsyncErrorHandling。顾名思义,这两个方法是分开处理的:callWithErrorHandling:处理同步方法的异常;callWithAsyncErrorHandling:处理异步方法的异常。使用方法如下:callWithAsyncErrorHandling(handler,instance,ErrorCodes.COMPONENT_EVENT_HANDLER,args)代码实现大致如下://packages/runtime-core/src/errorHandling.ts//handleexceptionsofsynchronousmethodsexportfunctioncallWithErrorHandling(fn:Function,instance:ComponentInternalInstance|null,type:ErrorTypes,args?:unknown[]){让res试试{res=args?fn(...args):fn();//调用原方法}catch(err){handleError(err,instance,type)}returnres}//处理异步方法的异常exportfunctioncallWithAsyncErrorHandling(fn:Function|Function[],instance:ComponentInternalInstance|null,type:ErrorTypes,args?:unknown[]):any[]{//省略其他代码constres=callWithErrorHandling(fn,instance,type,args)if(res&&isPromise(res)){res.catch(err=>{handleError(err,instance,type)})}//其他代码省略}callWithErrorHandling方法的逻辑比较简单,通过简单的try...catch做了一层封装。callWithAsyncErrorHandling方法比较巧妙,将要执行的方法传入callWithErrorHandling方法进行处理,通过.catch方法处理结果。2.异常处理上面代码中,如果报错,会通过handleError()处理异常。它的实现大致如下://packages/runtime-core/src/errorHandling.ts//异常处理方法exportfunctionhandleError(err:unknown,instance:ComponentInternalInstance|null,type:ErrorTypes,throwInDev=true){//省略其他代码logError(err,type,contextVNode,throwInDev)}functionlogError(err:unknown,type:ErrorTypes,contextVNode:VNode|null,throwInDev=true){//省略其他代码console.error(err)}保留核心处理逻辑之后,可以看到这里的处理也很简单,直接通过console.error(err)输出错误内容。3、配置errorHandler自定义异常处理器在使用Vue3时,也支持指定自定义异常处理器来处理组件渲染函数和监听器执行过程中抛出的未捕获错误。调用该处理函数时,可以得到错误信息和对应的应用实例。文档参考:《errorHandler》使用方法如下,配置在项目的main.js文件中://src/main.jsapp.config.errorHandler=(err,vm,info)=>{//句柄errors//`info`是Vue特有的错误信息,比如错误所在的生命周期钩子}那么errorHandler()是什么时候执行的呢?我们继续看源码中handleError()的内容,可以发现://packages/runtime-core/src/errorHandling.tsexportfunctionhandleError(err:unknown,instance:ComponentInternalInstance|null,type:ErrorTypes,throwInDev=true){constcontextVNode=instance?instance.vnode:nullif(instance){//省略其他代码//读取errorHandler配置项constappErrorHandler=instance.appContext.config.errorHandlerif(appErrorHandler){callWithErrorHandling(appErrorHandler,null,ErrorCodes.APP_ERROR_HANDLER,[err,exposedInstance,errorInfo])return}}logError(err,type,contextVNode,throwInDev)}通过instance.appContext.config.errorHandler获取全局配置的自定义错误处理函数,存在时执行。当然,This也是通过前面定义的callWithErrorHandling来调用的。4.调用errorCaptured生命周期钩子在使用Vue3时,您还可以使用errorCaptured生命周期钩子来捕获来自后代组件的错误。文档参考:《errorCaptured》入参如下:(err:Error,instance:Component,info:string)=>?boolean这个hook会接收三个参数:error对象,发生错误的组件实例,以及包含错误字符串来源的消息。该钩子可以返回false以防止错误向上传播。有兴趣的同学可以通过文档查看具体的错误传播规则。使用方法如下,父组件监听onErrorCaptured生命周期(示例代码使用Vue3设置语法):子组件如下:当点击“发送消息”按钮时,控制台会输出错误:[errorCaptured]Error:[testonErrorCaptured]atProxy.sendMessage(Message.vue:36:15)at_createElementVNode.onClick._cache.._cache.(Message.vue:3:39)在callWithErrorHandling(runtime-core.esm-bundler.js:6706:22)在callWithAsyncErrorHandling(runtime-core.esm-bundler.js:6715):21)在HTMLButtonElement.invoker(runtime-dom.esm-bundler.js:350:13)Proxy{sendMessage:?,…}nativeeventhandler可以看到onErrorCaptured生命周期钩子正常执行,在子组件Message.vue中输出异常那么这是如何实现的呢?还是看errorHandling.ts中的handleError()方法://packages/runtime-core/src/errorHandling.tsexportfunctionhandleError(err:unknown,instance:ComponentInternalInstance|null,type:ErrorTypes,throwInDev=true){constcontextVNode=实例?instance.vnode:nullif(instance){letcur=instance.parent//暴露的实例是渲染代理以保持它与2.x一致constexposedInstance=instance.proxy//在生产中hook只接收错误代码consterrorInfo=__DEV__吗?ErrorTypeStrings[type]:typewhile(cur){consterrorCapturedHooks=cur.ec//①取出组件配置的errorCaptured生命周期方法if(errorCapturedHooks){//②循环每个errorCapturedHookfor(leti=0;i={//省略其他[LifecycleHooks.RENDER_TRACKED]:'renderTrackedhook',[LifecycleHooks.RENDER_TRIGGERED]:'renderTriggeredhook',[ErrorCodes.SETUP_FUNCTION]:'setupfunction',[ErrorCodes.RENDER_FUNCTION]:'renderfunction',//省略其他[ErrorCodes.SCHEDULER]:'schedulerflush.这可能是一个Vue内部错误。'+'Pleaseopenanissueathttps://new-issue.vuejs.org/?repo=vuejs/vue-next'}遇到不同的错误,根据错误码ErrorCodes获取ErrorTypeStrings错误信息://packages/runtime-core/src/errorHandling.tsfunctionlogError(err:unknown,type:ErrorTypes,contextVNode:VNode|null,throwInDev=true){if(__DEV__){constinfo=ErrorTypeStrings[type]warn(`未处理的错误${info?`在执行${info}`期间:``}`)//省略其他}else{console.error(err)}}6.实现TreeShaking关于Vue3实现TreeShaking的介绍,可以看我之前写的高效实现框架和JS库瘦身,在logError方法就用它://packages/runtime-core/src/errorHandling.tsfunctionlogError(err:unknown,type:ErrorTypes,contextVNode:VNode|null,throwInDev=true){if(__DEV__){//省略其他}else{console.error(err)}}编译到生产环境时,__DEV__分支的代码不会被打包,从而优化包的大小。4.总结以上部分,我们差不多搞清楚了Vue3中全局异常处理的核心逻辑。我们在开发自己的错误处理方法时,也可以考虑这些核心点:支持同步和异步异常处理;设置业务错误代码和业务错误信息;支持自定义错误处理方法;支持开发环境错误提示;支持摇树。这几点大家在设计插件的时候可以考虑到~