当前位置: 首页 > 科技观察

React,优雅地捕捉异常

时间:2023-03-12 18:25:40 科技观察

前言人无完人,所以代码总会出错,出错并不可怕,关键是如何处理。就是想问问大家在应用react的时候如何捕捉错误呢?这时:小白+++:怎么处理?小白++:ErrorBoundary小白+:ErrorBoundary,trycatch小黑#:ErrorBoundary,trycatch,window.onerror小黑##:这是个严重的问题,我知道N种处理方法,你有什么更好的解决方案?ErrorBoundaryEerrorBoundary是16版本出来的,有人问我15版本怎么样,我没听。反正我用的是16,当然15有unstable_handleError。ErrorBoundary官网的介绍比较详细,这不是重点,重点是他能捕捉到什么异常。子组件的渲染生命周期函数构造函数;//YoucanalsologtheerrortoanerrorreportingservicelogErrorToMyService(error,info);}render(){if(this.state.hasError){//YoucanrenderanycustomfallbackUIreturn

Somethingwentwrong.

;}returnthis.props.children;}}开源世界是好的,已经有大神封装了react-error-boundary[1]这么优秀的库。发生错误后你只需要关心你需要关心的,一个Reset就完美了。import{ErrorBoundary}from'react-error-boundary'functionErrorFallback({error,resetErrorBoundary}){return(

出错了:

{error.message}
再次尝试
)}constui=({//resetthestateofyourappsotheerrordoesn'thappenagain}}>)不幸的是,错误边界不捕获这些错误:事件处理程序异步代码(例如setTimeout或requestAnimationFrame回调)服务器端渲染代码错误边界抛出错误本身见官网introducing-error-boundaries[2]什么本文要捕捉的是事件处理程序的错误。其实官方的方案是how-about-event-handlers[3],也就是trycatch。但是,事件处理程序那么多,我的天哪,得写多少。...................handleClick(){try{//Dosomethingthatcouldthrow}catch(error){this.setState({error});}}除了ErrorBoundary,我们再看一张表,上面列出了我们可以捕获的手段和范围例外。异常类型同步方法异步方法资源加载Promiseasync/await异常类型同步方法异步方法资源加载Promiseasync/awaittry/catch√√window.onerror√√error√√√unhandledrejection√√try/catch可以捕获同步和async/等待异常。window.onerror,错误事件window.addEventListener('error',this.onError,true);window.onerror=this.onErrorwindow.addEventListener('error')这个比window.onerror可以捕获更多的资源记录异常。请注意最后一个参数为true,false可能不是你预期的那样。当然,如果你问第三个参数的含义,我不想和你多说。再见。请注意,unhandledrejection的最后一个参数为真。window.removeEventListener('unhandledrejection',this.onReject,true)它捕获了Promise没有捕获到的异常。XMLHttpRequest和fetchXMLHttpRequest很容易处理,它们都有onerror事件。当然,99.99%的你不会自己封装一个基于XMLHttpRequest的库,axios真香,有完备的错误处理机制。至于fetch,自己跑withcatch,不处理就是你自己的问题。这么多,太难了。幸运的是,其实有一个库react-error-catch[4],它是一个基于ErrorBoudary、error和unhandledrejection封装的组件。其核心如下;使用:importErrorCatchfrom'react-error-catch'constApp=()=>{return({console.log('Errorreporting');//向后端上报异常信息,动态创建标签方法newImage().src=`http://localhost:3000/log/report?info=${JSON.stringify(errors)}`}}>
)}exportdefault拍手,拍手。其实并不是这样的:error捕获的错误最重要的是提供错误堆栈信息,这对于分析错误是相当不友好的,尤其是打包之后。错误太多了,我先处理一下React中的事件处理器。至于其他的,待续。事件处理程序的异常捕获示例我的想法很简单,用decorator[5]重写了原来的方法。先看使用:@methodCatch({message:"创建订单失败",toast:true,report:true,log:true})asynccreateOrder(){constdata={...};constres=awaitcreateOrder();if(!res||res.errCode!==0){returnToast.error("创建订单失败");}......其他可能产生异常的代码......Toast.success("Ordercreatedsuccessful");}注意四个参数:message:出错时打印的错误toast:发生错误,是否Toastreport:发生错误,是否上报日志:使用console.error印刷可能你会说,这个,新闻死了,不合理。如果我有任何其他消息。这时,我微微一笑,不着急,再看一段代码@methodCatch({message:"创建订单失败",toast:true,report:true,log:true})asynccreateOrder(){constdata={...};constres=awaitcreateOrder();if(!res||res.errCode!==0){returnToast.error("创建订单失败");}......其他可能产生异常的代码.......thrownewCatchError("创建订单失败,请联系管理员",{toast:true,report:true,log:false})Toast.success("创建订单成功");}是的,是的,您可以通过抛出自定义CatchError来覆盖以前的默认选项。这个方法Catch可以捕获,同步错误和异步错误,我们来看一下整个代码。类型定义exportinterfaceCatchOptions{report?:boolean;message?:string;log?:boolean;toast?:boolean;}//这里写const.ts比较合理exportconstDEFAULT_ERROR_CATCH_OPTIONS:CatchOptions={report:true,message:"unknownexception",log:true,toast:false}CustomCatchErrorimport{CatchOptions,DEFAULT_ERROR_CATCH_OPTIONS}from"@types/errorCatch";exportclassCatchErrorextendsError{public__type__="__CATCH_ERROR__";参数*/constructor(message:string,publicoptions:CatchOptions=DEFAULT_ERROR_CATCH_OPTIONS){super(message);}}装饰器importToastfrom"@components/Toast";import{CatchOptions,DEFAULT_ERROR_CATCH_OPTIONS}from"@typess/errorCatch";import{CatchError}来自“@util/error/CatchError”;constW_TYPES=[“string”,“object”];exportfunctionmethodCatch(options:string|CatchOptions=DEFAULT_ERROR_CATCH_OPTIONS){consttype=typeofoptions;letopt:CatchOptions;if(options==null||!W_TYPES.includes(type)){//null或不是字符串或者对象opt=DEFAULT_ERROR_CATCH_OPTIONS;}elseif(typeofoptions==="string"){//字符opt={...DEFAULT_ERROR_CATCH_OPTIONS,message:options||DEFAULT_ERROR_CATCH_OPTIONS.message,}}else{//有效的对象opt={...DEFAULT_ERROR_CATCH_OPTIONS,...options}}returnfunction(_target:any,_name:string,descriptor:PropertyDescriptor):any{constoldFn=descriptor.value;Object.defineProperty(descriptor,"value",{get(){asyncfunctionproxy(...args:any[]){try{constres=awaitoldFn.apply(this,args);returnres;}catch(err){//if(errinstanceofCatchError){if(err.__type__=="__CATCH_ERROR__"){errerr=errasCatchError;constmOpt={...opt,...(err.options||{})};if(mOpt.log){console.error("asyncMethodCatch:",mOpt.message||err.message,err);}if(mOpt.report){//TODO::}if(mOpt.toast){Toast.error(mOpt.message);}}else{constmessage=err.message||opt.message;console.error("asyncMethodCatch:",message,err);if(opt.toast){Toast.error(message);}}}}proxy._bound=true;returnproxy;}})returndescriptor;}}总结使用装饰器重写原来的方法,达到捕获错误的目的自定义错误类并抛出,达到覆盖默认选项的目的,增加灵活性。@methodCatch({message:"创建订单失败",toast:true,report:true,log:true})asynccreateOrder(){constdata={...};constres=awaitcreateOrder();if(!res||res.errCode!==0){returnToast.error("创建订单失败");}Toast.success("创建订单成功");......其他可能产生异常的代码........thrownewCatchError("创建订单失败,请联系管理员",{toast:true,report:true,log:false})}下一步是什么?不,还有很长的路要走。这是一个基本版本。1、扩展功能,支持更多类型和hooks版本。@XXXCatchclasssAAA{@YYYCatchmethod=()=>{}}2.抽象,再抽象,再抽象,笑话结束,严肃点:目前方案存在的问题:功能限制,抽象不够获取选项,代理函数,和错误处理函数完全可以分离,变成通用方法。同步方法转化为异步方法。所以理论上,需要区分同步和异步方案。在错误处理函数和异常如何处理之后,我们将围绕这些问题继续展开。Hooks版本的朋友说,现在这个时代,已经没有人还在用Hooks了。是的,大佬们说的对,我们要与时俱进。Hooks基础版已经有了,先分享使用,后续文章跟进。Hook的名称是useCatchconstTestView:React.FC=function(props){const[count,setCount]=useState(0);constdoSomething=useCatch(asyncfunction(){console.log("doSomething:begin");thrownewCatchError("doSomethingerror")console.log("doSomething:end");},[],{toast:true})constonClick=useCatch(async(ev)=>{console.log(ev.target);setCount(count+1);doSomething();constd=delay(3000,()=>{setCount(count=>count+1);console.log()});console.log("delaybegin:",Date.now())awaitd.run();console.log("delayend:",Date.now())console.log("TestView",this)thrownewCatchError("自定义异常,你不知道")},[count],{message:"Iamsosorry",toast:true});return
点我
{count}
}exportdefaultReact.memo(TestView);至于思路,基于useMemo,可以先看代码??:exportfunctionuseCatchany>(callback:T,deps:DependencyList,options:CatchOptions=DEFAULT_ERRPR_CATCH_OPTIONS):T{constopt=useMemo(()=>getOptions(options),[选项]);constfn=useMemo((..._args:any[])=>{constproxy=observerHandler(callback,undefined,function(error:Error){commonErrorHandler(error,opt)});returnproxy;},[callback,deps,opt])asT;returnfn;}写在最后error-boundaries[6]React异常处理[7]catching-react-errors[8]react高级异常处理机制-错误边界[9]decorator[10]core-decorators[11]autobind.js[12]