浅谈如何优雅处理JavaScript异步错误


Posted in Javascript onNovember 12, 2019

1. try/catch

try/catch基本上是大家最常和async/await一起使用的,基本上我们会用它去包围大部分的异步方法。await关键字后面的promise一旦reject了,就会抛出一个异常错误。

run();
async function run() {
  try {
    await Promise.ject(new Error('Oops!'));
  } catch (err) {
    console.error(error.message);
  }
}

try/catch同样也可以处理同步的错误,比如下面:

async function run() {
 const v = null;
 try {
  await Promise.resolve('foo');
  v.thisWillThrow;
 } catch (error) {
    // 会出现"TypeError: Cannot read property 'thisWillThrow' of null"
   console.error(error.message);
 }
}

好像我们只要无脑把逻辑都放到try/catch里面就万事大吉了吗?不太准确,下面的代码却会导致unhandled promise rejection。这个return关键字直接返回就错误却不会被捕获:

async function run() {
  try {
    // 直接返回Promise,而不是用await关键字
    return Promise.reject(new Error('Oops!'));
  } catch (error) {
    console.error(error.message);    
  }
}

一种处理方式是使用return await来解决。

try catch捕获不了回调函数。try catch 仅仅在单一执行环境中奏效。是在回调中加入try catch 来捕获错误。

setTimeout(funciton() {
 try {
  fn()
 } catch (e) {
   // handle error
 }     
      
})

这是奏效的。 不过try catch会在各个地方。 V8引擎是不鼓励try catch在函数中的使用的。 之前把try catch移到顶层来捕获调用栈的错误,但这个对异步代码不会奏效。

2. Golang-style(then)

golang style即使用.then()的方法来将一个promise转换为另一个处理完错误的reject promise。可以使用类似if(err)来进行检查:

async function throwAnError() {
  throw new Error('Opps!');
}

async function runAwait() {
  let err = await throwAnError();
  if (err){
    console.error(err.message);
  }
}

这么写会直接抛出异常,因为这个方法抛出了异常,但是该方法本身没有用try/catch捕获。很多时候,我们在使用第三方库的时候可能会出现这种情况。

then()解决方法

async function runAwait() {
    let err = await throwAnError().then(() => null, err => err);
  if (err){
    console.error(err.message);
  }
}

then()的方式,就会等待promise状态resolve或reject后然后执行相应的回调,然后判断err对象并处理,所以其实它相当于被捕获了。

同时返回错误和值

async function run() {
  let [err, res] = await throwAnError().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,这样可以保证所有错误都被捕获到。其实这也是一个原则,即统一处理错误,而不是单独去处理每个错误

async function run() {
 return Promise.reject(new Error('Oops!'));
}

run().catch(function handleError(err) {
  console.error(err.message);
}).catch( err => {
  process.nextTick(() => { throw errl});
})

使用catch捕获错误,如果handleError本身也有错误,就需要再catch一遍,但是为了避免回调地狱,如果该方法发生了错误就终止该进程。

优缺点

  • 使用catch的话,不管异步方法本身是否捕获错误,它都会去捕获异步错误。
  • 使用try/catch无法避免catch本身抛出异常,而如果它抛出了那除了嵌套多一层try/catch外,最好的做法就是加catch来让代码更简洁。

4  全局错误捕获

4.1 浏览器全局错误捕获

浏览器全局处理基本上就是依靠事件,因为浏览器是事件驱动的。一旦抛出错误,解释器在执行环境上下文中停止执行并展开,此时会有一个onerror全局事件抛出:

window.addEventListener('error', function (e) {
  var error = e.error;
  console.log(error);
})

全局错误处理器会捕获任何在执行环境中发生的错误,即便是不同的对象发生的错误事件,或者是各种类型的错误。这是全局集中处理错误的一种常见方式。

调用栈

调用栈在定位问题的时候十分重要,我们可以使用调用栈在处理器中处理特定的错误。

window.addEventListener('error', function (e) {
 var stack = e.error.stack;
 var message = e.error.toString();
 if (stack) {
  message += '\n' + stack;
 }
 var xhr = new XMLHttpRequest();
 xhr.open('POST', '/log', true);
 // Fire an Ajax request with error details
 xhr.send(message);
});

通过日志可以看到,具体什么情况触发了什么错误。在调试时调用堆栈也会非常有用。你 可以分析log,看到什么条件下触发了错误。

浅谈如何优雅处理JavaScript异步错误

注意:

如果跨域脚本是不会看到错误的。 在JS中,错误信息仅仅是允许在同一个域中。

个人想法

更多的时候,代码抛出了异常,我们更关注的是在运行时,某个变量的值是什么,是否这个变量的值导致了错误,所以打印出调用时的跟多的信息更重要。

4.2 Node.js全局错误捕获

Node.js本身的异常处理要复杂得多,因为涉及到了进程或线程抛出异常的问题。

基于Koa的全局错误处理

nodejs是error-first的异步处理机制,此处底层会调用net模块的listen方法并在错误发生时执行回调。

app.listen(app.config.listenPort, (err) => {
 if (err) throw err
 app.logger.info(`> Ready on http://localhost:${app.config.listenPort}`)
})

路由错误处理

对于每个路由,它可能也会有不同的错误处理逻辑,这时路由进来的请求就需要根据情况返回不同的异常码和信息。

router.get('/loginAuth', async (ctx, next) => {
 try {
  const code = query.code
  const res = await requestToken(code)
  if (res.data.code !== 0) {
   ctx.app.logger.error(`request token error.Code is ${res.data.code} || response is: ${JSON.stringify(res.data.data)} || msg: ${res.data.message}`)
   ctx.body = {
    code: 10000,
    message: `request token by code error`
   }
  } else {
   ctx.body = res.data
  }
 } catch (err) {
  ctx.app.logger.error(`request api has exception ${ctx.request.url} || ${err.code} || ${err.message} || ${err.stack}`)
  ctx.body = {
   code: 500,
   message: `Error response`
  }
 }
})

5. 总结

  1. 通常异常可能是预期的或者超出预期的,不管怎样,使用try/catch没有问题。
  2. 对于超出预期的错误,尽量使用catch来保证它们会被捕获到。
  3. 把错误处理器添加到window对象上,它会捕获到异步错误,符合了DRY和SOLID原则。一个全局的错误处理器可以帮你保持异步代码整洁。

Reference

async-await-error-handling
nodejs-v12-lts

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
js编码、解码函数介绍及其使用示例
Sep 05 Javascript
使用CSS3的scale实现网页整体缩放
Mar 18 Javascript
JavaScript 作用域链解析
Nov 13 Javascript
javascript每日必学之多态
Feb 23 Javascript
jQuery zTree加载树形菜单功能
Feb 25 Javascript
JavaScript实现广告弹窗效果
Aug 09 Javascript
javascript 初学教程及五子棋小程序的简单实现
Jul 04 Javascript
JS原生数据双向绑定实现代码
Aug 14 Javascript
浅谈js中的this问题
Aug 31 Javascript
vue模仿网易云音乐的单页面应用
Apr 24 Javascript
详解微信小程序胶囊按钮返回|首页自定义导航栏功能
Jun 14 Javascript
5种方法告诉你如何使JavaScript 代码库更干净
Sep 15 Javascript
vue页面切换项目实现转场动画的方法
Nov 12 #Javascript
解决vue-cli 打包后自定义动画未执行的问题
Nov 12 #Javascript
vue transition 在子组件中失效的解决
Nov 12 #Javascript
vue+element导航栏高亮显示的解决方式
Nov 12 #Javascript
vue-element-admin 菜单标签失效的解决方式
Nov 12 #Javascript
Vue退出登录时清空缓存的实现
Nov 12 #Javascript
解决vue admin element noCache设置无效的问题
Nov 12 #Javascript
You might like
PHP实现HTTP断点续传的方法
2015/06/17 PHP
在b/s开发中经常用到的javaScript技术
2006/08/23 Javascript
javascript showModalDialog 内跳转页面的问题
2010/11/25 Javascript
键盘上一张下一张兼容IE/google/firefox等浏览器
2014/01/28 Javascript
jquery无刷新验证邮箱地址实现实例
2014/02/19 Javascript
两种方法实现在HTML页面加载完毕后运行某个js
2014/06/16 Javascript
javascript快速排序算法详解
2014/09/17 Javascript
Jquery遍历Json数据的方法
2015/04/20 Javascript
js实现基于正则表达式的轻量提示插件
2015/08/29 Javascript
Javascript简单实现面向对象编程继承实例代码
2015/11/27 Javascript
Angular4自制一个市县二级联动组件示例
2017/11/21 Javascript
JS排序算法之希尔排序与快速排序实现方法
2017/12/12 Javascript
mpvue 如何使用腾讯视频插件的方法
2018/07/16 Javascript
深入理解Vue router的部分高级用法
2018/08/15 Javascript
angularJs中json数据转换与本地存储的实例
2018/10/08 Javascript
vue+canvas实现炫酷时钟效果的倒计时插件(已发布到npm的vue2插件,开箱即用)
2018/11/05 Javascript
Vue执行方法,方法获取data值,设置data值,方法传值操作
2020/08/05 Javascript
学习 Vue.js 遇到的那些坑
2021/02/02 Vue.js
python 解析XML python模块xml.dom解析xml实例代码
2014/02/07 Python
探究数组排序提升Python程序的循环的运行效率的原因
2015/04/01 Python
Django中login_required装饰器的深入介绍
2017/11/24 Python
详解pycharm连接不上mysql数据库的解决办法
2020/01/10 Python
浅谈python之自动化运维(Paramiko)
2020/01/31 Python
Python3如何使用tabulate打印数据
2020/09/25 Python
详解CSS3中字体平滑处理和抗锯齿渲染
2017/03/29 HTML / CSS
美国单身专业人士在线约会网站:EliteSingles
2019/03/19 全球购物
三星俄罗斯授权在线商店:Samsung俄罗斯
2019/09/28 全球购物
大学生军训自我鉴定
2014/02/12 职场文书
金融专业求职信
2014/08/05 职场文书
学院党的群众路线教育实践活动整改方案
2014/10/04 职场文书
个人债务授权委托书范本
2014/10/05 职场文书
资产运营委托书范本
2014/10/16 职场文书
2015年科协工作总结
2015/05/19 职场文书
Mysql调整优化之四种分区方式以及组合分区
2022/04/13 MySQL
科学家测试在太空中培育人造肉,用于未来太空旅行
2022/04/29 数码科技
css清除浮动clearfix:after的用法详解(附完整代码)
2023/05/21 HTML / CSS