如何在使用async & await 时优雅的处理异常

原文来自: blog.grossman.io/how-to-writ…javascript

在ES7的中,咱们可使用async & await进行编写异步函数,使用这种写法咱们的异步函数看起来就跟同步代码同样。java

在以前的版本(ES6),可使用Promise写法,来简化咱们异步编程的流程,同时也避免了回调地狱node

回调地狱

回调地狱是语义化产生的一个术语,它的释义能够用下面这种状况进行阐述:git

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。github

使用Promise优化

ES6和Promise的出现,使得咱们能够简化以前"回调地狱"般的代码以下:数据库

function asyncTask(cb) {
   asyncFuncA.then(AsyncFuncB)
      .then(AsyncFuncC)
      .then(AsyncFuncD)
      .then(data => cb(null, data)
      .catch(err => cb(err));
}
复制代码

这样编写是否是看起来舒服多了?npm

可是在实际的业务场景中,异步流的处理可能会更加复杂一些。举例来讲,编程

假如在你的一个(node.js)服务器中,你可能想要:数组

  1. 将一个数据1保存到数据库中 (步骤1)
  2. 根据保存的数据1查找另一个数据2 (步骤2)
  3. 若是查找到了数据2,执行其余的一些异步任务 (其余任务)
  4. 等到全部的任务所有执行完成以后,你可能须要使用你在(步骤1)中获得的结果用来反馈给用户。
  5. 假如在执行任务的过程当中发生了错误,你得要告诉用户在哪一个步骤发生了错误。

在使用了Promise语法后,这样固然看起来更加的简洁了,可是,在我看来仍然有一点混乱。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返回值

async & await 异常捕捉工具函数

有了这些知识,咱们就能够制做一个小的通用函数来帮助咱们捕捉这些错误。

// 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和许多其余解决方案来实现相似的结果。 只要你喜欢而且它适用。

引起思考

async/await 结合promise使用就行了, 即异步流程控制的最后加上 promise的catch.

async function task(){
    return await req();
}

task().catch(e => console.error(e))
复制代码

结合async/await 结合Promise.all使用时, 如何捕获异常&处理?

async function hello(flag){
    return new Promise((resolve, reject) => {
        if(flag) setTimeout(() => resolve('hello'), 100);
        else reject('hello-error');
    })
}

async function demo(flag){
    return new Promise((resolve, reject) => {
        if(flag) setTimeout(() => resolve('demo'), 100);
        else reject('demo-error');
    })
}

async function main(){
    let res = await hello(1).catch(e => console.error(e));
    console.log('res => ', res);
    let result = await Promise.all([hello(1), demo(1)]);
    // let result = await Promise.all([hello(1), demo(0)]).catch(e => console.error('error => ', e));
    console.log('result => ', result);
}

main()
复制代码
相关文章
相关标签/搜索