如何不使用try-catch块在Javascript中编写异步等待

ES7异步/ AWAIT使我们的开发人员编写异步JS代码看起来同步。在目前的JS版本中,我们将介绍Promises,这样我们可以简化Async流程并避免回调地狱。

回调地狱是用来描述JS中的以下情况的术语:

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语句,需要执行其他Async方法,如果某些来自callbackA的结果等于'foo'。

承诺救援

凭借承诺和ES6,我们可以简化我们以前的代码噩梦,如下所示:

function asyncTask(cb) {

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

看起来好多了,你不觉得

但是在现实世界的场景中,异步流可能会更复杂一些,例如在服务器模型(nodejs)中,您可能希望将实体保存到数据库,然后根据保存的值查找其他实体,如果该值存在执行其他异步任务,所有任务完成后,您可能希望使用步骤1中创建的对象对用户进行响应。如果您希望通知用户确切错误的步骤之一发生错误。

有承诺,当然,它会看起来更清洁,然后用简单的回调,但仍然可以得到一个凌乱的IMHO。

ES7异步/等待

注意:为了享受异步/等待,您将需要使用透明机,您可以使用所需的多媒体文字或者打字稿。

这是我发现异步等待真正有用的地方,它允许你编写如下代码:

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);
}

上面的代码看起来更清洁,但是,如何处理错误?

在执行异步调用时,在执行承诺期间可能会发生什么(数据库连接错误,数据库模型验证错误等)

由于异步函数正在等待Promises,当承诺遇到错误时,会抛出一个异常,该异常将被捕获在承诺的catch方法中。

在异步/等待功能中,通常使用try / catch块来捕获这样的错误。

我不是来自一个类型的语言背景,所以try / catch增加了我附加的代码,在我看来,没有看起来干净。我确定这是个人偏好的问题,但这是我的意见。

所以以前的代码看起来像这样:

async function asyncTask(cb) {  
    try {
       const user = await UserModel.findById(1);
       if(!user) return cb('No user found');
    } catch(e) {
        cb('Unexpected error occurred');
    }

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

    if(user.notificationsEnabled) {
        try {
            await NotificationService.sendNotification(user.id, 'Task Created');  
        } catch(e) {
            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) {
            cb('Error while sending notification');
        }
    }

    cb(null, savedTask);
}

一种不同的做事方式

最近我一直用go-lang编码,真的很喜欢他们的解决方案,看起来像这样:

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

我认为它更干净,然后使用try-catch块,并减少代码集,这使得它可读和可维护。

但是等待的问题是,如果没有提供try-catch块,它将默认退出你的功能。除非提供catch子句,否则您将无法控制它。

当我和Tomer Barnea一个好朋友坐下来,试图找到一个更清洁的解决方案,我们完成了使用下一个方法:

请记住,等待着等待解决的承诺?

有了这些知识,我们可以使小功效来帮助我们抓住这些错误:

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

效用函数接收到一个承诺,然后将返回数据作为第二个项目解析成一个数组的成功响应。而收到的错误是第一个。

然后我们可以使我们的异步代码看起来像这样:

JS
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);
}

上面的例子只是解决方案的一个简单用例,您可以在to.js方法中附加拦截器,该方法将接收原始错误对象,记录或执行任何您需要执行的操作,然后再传回。

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

npm i await-to-js  
展开阅读全文

没有更多推荐了,返回首页