如何在Javascript中优雅的使用Async和Await进行错误的处理?

ES7的出现,允许我们使用Async和Await进行编写异步函数,使用这种写法我们的异步函数看起来就跟同步代码一样。在之前的版本,我们引入了Promise写法,来简化我们异步编程的流程,同时也避免了回调地狱。

回调地狱是语义化产生的一个术语,他的释义可以用下面这种情况进行阐述:

function AsyncTask() {
   asyncFuncA(function(err, resultA){
      if(err) return cb(err);

      asyncFuncB(function(err, resultB){
         if(err) return cb(err);

          asyncFuncC(function(err, resultC){
               if(err) return cb(err);

               // And so it goes....
          });
      });
   });
}

不断的回调,使得代码维护和管理控制流程变得十分的困难。我们不妨考虑下这种情况,假如某个if语句需要执行其他的方法,而回调函数FunctionA的结果为foo。

使用Promise来改善这种情况

ES6和Promise的出现,我们得以简化之前梦魇般的回调代码如下:

function asyncTask(cb) {

   asyncFuncA.then(AsyncFuncB)
      .then(AsyncFuncC)
      .then(AsyncFuncD)
      .then(data => cb(null, data)
      .catch(err => cb(err));
}

这样编写是不是看起来更加棒了?

但是在真实场景中,异步流可能会变得更加复杂一些。举例来说,

假如在你的一个(node.js)服务器中,你可能想要将一个数据1保存到数据库中,(步骤1)
然后根据保存的数据1查找另外一个数据2,(步骤2)
如果查找到了数据2,执行其他的一些异步任务,(其他任务),
等到所有的任务全部执行完成之后,你可能需要使用你在步骤1中得到的结果用来反馈给用户。
并且假如在执行任务的过程中发生了错误,你想要告诉用户在哪个步骤发生了错误。

在使用了Promise语法后,这样当然看起来更加的简洁了,但是,在我看来仍然有一点混乱。

ES7 Async/await

注意:您需要使用转译器才能使用Async/Await,您可以使用babel插件或Typescript来添加所需的工具。

这是我发现的 async/await 确实有用,它允许我们像下面一样编写代码:

async function asyncTask(cb) {
    const user = await UserModel.findById(1);
    if(!user) return cb('No user found');

    const savedTask = await TaskModel({userId: user.id, name: 'Demo Task'});

    if(user.notificationsEnabled) {
         await NotificationService.sendNotification(user.id, 'Task Created');  
    }

    if(savedTask.assignedUser.id !== user.id) {
        await NotificationService.sendNotification(savedTask.assignedUser.id, 'Task was created for you');
    }

    cb(null, savedTask);
}

上面的代码看起来更加的干净了,但是如何处理错误报错呢?

在执行异步任务使用Promise的时候可能会发生一些错误类似数据库连接出错,数据库模型验证错误等情况。

当一个异步函数正在等待Promise返回值的时候,当Promise方法报了错误的时候,它会抛出异常,这个异常可以在catch方法里面捕获到。

在使用Async/Await时,我们通常使用try/catch语句进行异常捕获。

try{
    //do something
}
catch{
   // deal err
}

我不是一个来自强类型背景的人,所以额外的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);
}

一种不同的处理方式:

最近我一直在使用go-lang进行编码,并且非常喜欢他们的解决方案,它的代码看起来像这样:

data, err := db.Query("SELECT ...")
if err != nil { return err }

我认为它比使用try-catch语句块更加简洁,并且代码量更少,这使得它可读和可维护更好。

但是使用Await的话,如果没有为其提供try-catch处理异常的话,当程序发生错误的时候,它会默默的退出(你看到不抛出的异常)。假如你没有提供catch语句来捕捉错误的话,你将无法控制它。

当我和Tomer Barnea(我的好朋友)坐在一起并试图找到一个更简洁的解决方案时,我们得到了下一个使用方法:

请记住,Await在等待一个Promise返回值

有了这些知识,我们就可以制作一个小的通用函数来帮助我们捕捉这些错误。

// to.js
export default function to(promise) {
   return promise.then(data => {
      return [null, data];
   })
   .catch(err => [err]);
}

这个通用函数接收一个Promise,然后将处理成功的返回值以数组的形式作为附加值返回,并且在catch方法中接收到捕捉到的第一个错误。

import to from './to.js';

async function asyncTask(cb) {
     let err, user, savedTask;

     [err, user] = await to(UserModel.findById(1));
     if(!user) return cb('No user found');

     [err, savedTask] = await to(TaskModel({userId: user.id, name: 'Demo Task'}));
     if(err) return cb('Error occurred while saving task');

    if(user.notificationsEnabled) {
       const [err] = await to(NotificationService.sendNotification(user.id, 'Task Created'));  
       if(err) return cb('Error while sending notification');
    }

    cb(null, savedTask);
}

上面的例子只是一个使用该解决方案的简单用例,你可以在io.js中添加拦截方法(类似调试的断点),该方法将接收原始错误对象,打印日志或者进行其他任何你想要进行的操作,然后再返回操作后的对象。

我们为这个库创建了一个简单的NPM包,您可以使用以下方法进行安装:
Github Repo

npm i await-to-js

这篇文章只是寻找Async/Await功能的一种不同方式,完全基于个人意见。 您可以使用Promise,仅使用try-catch和许多其他解决方案来实现类似的结果。 只要你喜欢并且它适用。

原文地址:https://blog.grossman.io/how-to-write-async-await-without-try-catch-blocks-in-javascript/

猜你喜欢

转载自blog.csdn.net/xjl271314/article/details/79566447