async/await 如何优美的处理异常?
发布于 7 年前 作者 danielsss 17008 次浏览 来自 分享

本着一种学习的态度写了此话题,如有不妥之处还请各位大牛多多指点.

Purpose: 此解决方案,可以避免业务代码中出现大量的try/catch, 众所周知try/catch对性能方面有一定的影响, 另一方面try/catch在Node.js >= v8.3.0以后对性能的损耗是可以忽略不计的.

try/catch具体文献: However for Node 8.3+ the performance hit of calling a function inside a try block is negligible.

1. try/catch方法处理异常

async updateArticleById(ctx, next){
		 let id = ctx.params["id"];
       let body = ctx.request.body;
       try {
           let ret = await Services.admin.updateArticle(id, body);
           ctx.body = {ret: 0, data: ret};
       } catch(e) {
           ctx.body = {ret: 1, data: null, err: e.message || e.stack};
       }
}
  • 以上捕获异常是使用try/catch的方式来处理,因为await后面跟着的是Promise对象,当有异常的情况下会被Promise对象的内部
  • catch捕获,而await就是一个then的语法糖,并不会捕获异常, 那就需要使用try/catch来捕获异常,并进行相应的逻辑处理。

2. 封装异常处理函数

  • 知道了上面异常会被Promise对象自身的catch捕获异常,可以使用下面的解决方案 to.js
module.exports = (promise) => {
   if(!promise || !Promise.prototype.isPrototypeOf(promise)){
       return new Promise((resolve, reject)=>{
           reject(new Error("requires promises as the param"));
       }).catch((err)=>{
           return [err, null];
       });
   }
   return promise.then(function(){
       return [null, ...arguments];
   }).catch(err => {
       return [err, null];
   });
};

格式 [error, …result]

  • 采用类似Golang风格的错误返回方式, 这里指定第一个参数为错误参数,后面为正常返回结果
  • if块是用来处理非法参数,并返回错误[err, null]
  • await后面如果是一个promise对象,那么await的任务就是在等待promise.resolve,而to.js就是主动去调用then和catch,主动处理并重新封装结果,并且在then或是catch里面继续返回封装后的数据,返回值对于await来说仍然是一个promise对象,然而resolve的值却是一个可迭代的对象[error, …result]

这个可迭代的对象如何使用 ?

async updateArticleById(ctx, next){
       let body = ctx.request.body;
       let id = ctx.request.params["id"];
       let [err, ret] = await ctx.app.utils.to(Services.admin.updateArticleById(id, body));
       if(err) {
           return ctx.body = {ret: 1, data: null, err: err};
       }

       ctx.body = {ret: 0, data: ret};
   }
20 回复

arguments可直接用扩展运算符 ctx.app.utils.to(Services.admin.updateArticleById(id, body));没加await

@yuu2lee4 我以为arguments不会和array一样直接使用扩展运算符. 非常感谢,已经改正

新版本的V8引擎 try/catch 对性能没有影响

@151263 确实,我上面也说了!

try/catchif(err) 都不优雅,满屏捕获代码看着眼花,还是尽量在产生异常的地方处理掉……

可以使用这个, 参考了go的defer,在defer中捕获错误,这样也可以消除try catch

const godefer = require('godefer');

const getUserInfo = godefer(async function(name, defer){
  defer(function(err){
  	if (err){
		console.error(err);
	}
  })
  
  // ...
  await doSomthingAsync();
  await doAnotherSomthingAsync();
})

当前只是看起来好看一些,内部还是用try catch

不太明白,try catch 不是很自然的做法吗? 用if判断,如果漏掉呢?如果忘记return呢?

@178220709

  1. 目前Node.js < 8.3.0 try/catch 是多多少少会有性能影响
  2. 如果你忘记return那返回的结果就是第二次set body的值
  3. 如果你不想使用if, 那么你完全可以在to.js中的then和catch中直接处理掉
return promise.then(function(){
       ctx.body = {...}//normally handle
	   //return [null, ...arguments];
   }).catch(err => {
       ctx.body = {...}//exception handle
       //return [err, null];
});
  1. 如果使用try/catch代码效果是下面的样子,如果你能忍受代码这么写,确实使用try/catch更舒服,所谓萝卜青菜各有所爱,我就是有强迫症的人,完全受不了这种代码风格.
async function asyncTask(cb) {  
    try {
       const user = await UserModel.findById(1);
       if(!user) return cb('No user found');
    } catch(e) {
        return cb('Unexpected error occurred');
    }

    try {
       const savedTask = await TaskModel({userId: user.id, name: 'Demo Task'});
    } catch(e) {
        return cb('Error occurred while saving task');
    }

    if(user.notificationsEnabled) {
        try {
            await NotificationService.sendNotification(user.id, 'Task Created');  
        } catch(e) {
            return cb('Error while sending notification');
        }
    }

    if(savedTask.assignedUser.id !== user.id) {
        try {
            await NotificationService.sendNotification(savedTask.assignedUser.id, 'Task was created for you');
        } catch(e) {
            return cb('Error while sending notification');
        }
    }

    cb(null, savedTask);
}

@brickyang 确实是这样。 但是如果有些业务场景确实需要将异常发送给客户端,那就避免不了这种情况.

如果实在嫌弃这种代码不够优雅, 其实可以在to.js中直接response 8楼有代码示例

@danielsss 非得每一步都try catch吗, 你上我项目里找找, 能找到超过5个算你赢: https://github.com/xiaozhongliu/node-api-seed

@XiaozhongLiu 如果下面promisify(hget)出现异常,你如何处理错误呢?

async hget(key, field) {
        const value = await promisify(redis.hget)(key, field)
        try {
            return JSON.parse(value)
        } catch (e) {
            return value
        }
 }

@XiaozhongLiu 不错的后继处理方案,而前置处理貌似也没什么问题。总有一种方案是选择么

@danielsss 没看出来你非要前置做什么特殊处理. 遵循DRY原则没什么坏处, 反倒是你那一大段代码能缩减到10行以内.

之前也想过这个问题,但后面还是用了try catch去做,不过题主提供了不错的解决方法,值得借鉴

错误和异常不是一回事,异常应该是用在“可能会出现预料不到的结果” 比如查数据库,连接的时候可能会出现异常,但是连接成功以后,执行sql都应该只判断错误就可以了

@danielsss 正常的代码设计不都应该一个function一个try catch统一处理所有可能的异常么,所有可能的异常统一抛错误码就行了,哪有一步一个try catch的…除非是不需要中断的异常,那自己单独包个try不就好了

@PirateD311 你可以想象下你的api有很多个异步操作,每个异步操作后面还有一些逻辑运算,这种应用场景比比皆是. 你不可能一个trycatch 把所有代码都包进去吧? 自豪地采用 CNodeJS ionic

回到顶部