浅谈如何优雅处理JavaScript异步错误
1.try/catch
try/catch基本上是大家最常和async/await一起使用的,基本上我们会用它去包围大部分的异步方法。await关键字后面的promise一旦reject了,就会抛出一个异常错误。
run(); asyncfunctionrun(){ try{ awaitPromise.ject(newError('Oops!')); }catch(err){ console.error(error.message); } }
try/catch同样也可以处理同步的错误,比如下面:
asyncfunctionrun(){ constv=null; try{ awaitPromise.resolve('foo'); v.thisWillThrow; }catch(error){ //会出现"TypeError:Cannotreadproperty'thisWillThrow'ofnull" console.error(error.message); } }
好像我们只要无脑把逻辑都放到try/catch里面就万事大吉了吗?不太准确,下面的代码却会导致unhandledpromiserejection。这个return关键字直接返回就错误却不会被捕获:
asyncfunctionrun(){ try{ //直接返回Promise,而不是用await关键字 returnPromise.reject(newError('Oops!')); }catch(error){ console.error(error.message); } }
一种处理方式是使用returnawait来解决。
trycatch捕获不了回调函数。trycatch仅仅在单一执行环境中奏效。是在回调中加入trycatch来捕获错误。
setTimeout(funciton(){ try{ fn() }catch(e){ //handleerror } })
这是奏效的。不过trycatch会在各个地方。V8引擎是不鼓励trycatch在函数中的使用的。之前把trycatch移到顶层来捕获调用栈的错误,但这个对异步代码不会奏效。
2.Golang-style(then)
golangstyle即使用.then()的方法来将一个promise转换为另一个处理完错误的rejectpromise。可以使用类似if(err)来进行检查:
asyncfunctionthrowAnError(){ thrownewError('Opps!'); } asyncfunctionrunAwait(){ leterr=awaitthrowAnError(); if(err){ console.error(err.message); } }
这么写会直接抛出异常,因为这个方法抛出了异常,但是该方法本身没有用try/catch捕获。很多时候,我们在使用第三方库的时候可能会出现这种情况。
then()解决方法
asyncfunctionrunAwait(){ leterr=awaitthrowAnError().then(()=>null,err=>err); if(err){ console.error(err.message); } }
then()的方式,就会等待promise状态resolve或reject后然后执行相应的回调,然后判断err对象并处理,所以其实它相当于被捕获了。
同时返回错误和值
asyncfunctionrun(){ let[err,res]=awaitthrowAnError().then(v=>[null,v],err=>[err,null]); if(err){ console.error(err.message); } console.log(res) }
结果:这么做可以通过解构返回一个数组,包含了结果和error对象。当然如果是reject就会返回null和error对象;而如果resolved返回数组的第一个error对象就为null,第二个就是结果。
优缺点
优点:这种模式可以更简洁地处理,同时可以不需要写catch。
缺点1:这是非常重复性的,每次执行异步操作都需要去判断error对象。
缺点2:无法帮助处理run方法中的同步错误。
所以这种方式需要谨慎使用。
3.Catch捕获
上面两种模式都可以处理异步错误,但是对于错误处理,最好的情况是在异步逻辑的最后加上catch,这样可以保证所有错误都被捕获到。其实这也是一个原则,即统一处理错误,而不是单独去处理每个错误。
asyncfunctionrun(){ returnPromise.reject(newError('Oops!')); } run().catch(functionhandleError(err){ console.error(err.message); }).catch(err=>{ process.nextTick(()=>{throwerrl}); })
使用catch捕获错误,如果handleError本身也有错误,就需要再catch一遍,但是为了避免回调地狱,如果该方法发生了错误就终止该进程。
优缺点
- 使用catch的话,不管异步方法本身是否捕获错误,它都会去捕获异步错误。
- 使用try/catch无法避免catch本身抛出异常,而如果它抛出了那除了嵌套多一层try/catch外,最好的做法就是加catch来让代码更简洁。
4 全局错误捕获
4.1浏览器全局错误捕获
浏览器全局处理基本上就是依靠事件,因为浏览器是事件驱动的。一旦抛出错误,解释器在执行环境上下文中停止执行并展开,此时会有一个onerror全局事件抛出:
window.addEventListener('error',function(e){ varerror=e.error; console.log(error); })
全局错误处理器会捕获任何在执行环境中发生的错误,即便是不同的对象发生的错误事件,或者是各种类型的错误。这是全局集中处理错误的一种常见方式。
调用栈
调用栈在定位问题的时候十分重要,我们可以使用调用栈在处理器中处理特定的错误。
window.addEventListener('error',function(e){ varstack=e.error.stack; varmessage=e.error.toString(); if(stack){ message+='\n'+stack; } varxhr=newXMLHttpRequest(); xhr.open('POST','/log',true); //FireanAjaxrequestwitherrordetails xhr.send(message); });
通过日志可以看到,具体什么情况触发了什么错误。在调试时调用堆栈也会非常有用。你可以分析log,看到什么条件下触发了错误。
注意:
如果跨域脚本是不会看到错误的。在JS中,错误信息仅仅是允许在同一个域中。
个人想法
更多的时候,代码抛出了异常,我们更关注的是在运行时,某个变量的值是什么,是否这个变量的值导致了错误,所以打印出调用时的跟多的信息更重要。
4.2Node.js全局错误捕获
Node.js本身的异常处理要复杂得多,因为涉及到了进程或线程抛出异常的问题。
基于Koa的全局错误处理
nodejs是error-first的异步处理机制,此处底层会调用net模块的listen方法并在错误发生时执行回调。
app.listen(app.config.listenPort,(err)=>{ if(err)throwerr app.logger.info(`>Readyonhttp://localhost:${app.config.listenPort}`) })
路由错误处理
对于每个路由,它可能也会有不同的错误处理逻辑,这时路由进来的请求就需要根据情况返回不同的异常码和信息。
router.get('/loginAuth',async(ctx,next)=>{ try{ constcode=query.code constres=awaitrequestToken(code) if(res.data.code!==0){ ctx.app.logger.error(`requesttokenerror.Codeis${res.data.code}||responseis:${JSON.stringify(res.data.data)}||msg:${res.data.message}`) ctx.body={ code:10000, message:`requesttokenbycodeerror` } }else{ ctx.body=res.data } }catch(err){ ctx.app.logger.error(`requestapihasexception${ctx.request.url}||${err.code}||${err.message}||${err.stack}`) ctx.body={ code:500, message:`Errorresponse` } } })
5.总结
- 通常异常可能是预期的或者超出预期的,不管怎样,使用try/catch没有问题。
- 对于超出预期的错误,尽量使用catch来保证它们会被捕获到。
- 把错误处理器添加到window对象上,它会捕获到异步错误,符合了DRY和SOLID原则。一个全局的错误处理器可以帮你保持异步代码整洁。
Reference
async-await-error-handling
nodejs-v12-lts
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。