JavaScript异步编程: 从回调地狱到async和await

写好一个优秀的web应用关键之一就是可以在一个页面上做许多AJAX请求

前言

我们可以从一个简单的例子的每个解决方式来思考JavaScript异步编程的进步

为了做到这些,我们可以来做一个简单的任务,这个任务是完成下面这些流程:

  1. 验证用户的名称和密码
  2. 获取应用中用户的角色
  3. 打印用户访问应用的时间

回调地狱的方式

最古老的解决这些问题的方式是通过一层套一层的的回调。这在过去是解决简单的异步任务的优雅方式,但是呢,由于回调地狱的原因,并不能进行拓展升级。
在这里插入图片描述

用于解决三个简单问题的代码如下

const verifyUser = function(username, password, callback){
   dataBase.verifyUser(username, password, (error, userInfo) => {
       if (error) {
           callback(error)
       }else{
           dataBase.getRoles(username, (error, roles) => {
               if (error){
                   callback(error)
               }else {
                   dataBase.logAccess(username, (error) => {
                       if (error){
                           callback(error);
                       }else{
                           callback(null, userInfo, roles);
                       }
                   })
               }
           })
       }
   })
};

每个函数的调用都需要传递一个参数,这个参数也是一个函数,会接受前一个函数的返回值。

仅仅阅读上面的句子都会使得太多人大脑麻木,而如果一个应用拥有数以百计的这种代码的话,会对维护这些代码的人造成极大的困扰,即使是这些人自己写的这些代码(不要高估明天的自己,明天的你不一定可以看懂今天的你写的代码)。

当你知道database.getRoles`也是一个嵌套回调的函数的时候,你会意识到这个例子变得更加复杂。

const getRoles = function (username, callback){
   database.connect((connection) => {
       connection.query('get roles sql', (result) => {
           callback(null, result);
       })
   });
};

这些代码的问题,除了代码难以维护以外,还有就是违背DRY原则(Do not repeat yourself)。例如,异常处理在每个函数中都被重复的进行(if else)并且调用的callback也在每个嵌套的function中调用。

JavaScript中的Promises

Promise是避免回调地狱的一个进步。此方法并没有移除回调函数,但是将函数的调用连接起来并且简化了代码,使得代码更易于阅读。

在这里插入图片描述

使用了Promise的代码如下

const verifyUser = function(username, password) {
   database.verifyUser(username, password)
       .then(userInfo => dataBase.getRoles(userInfo))
       .then(rolesInfo => dataBase.logAccess(rolesInfo))
       .then(finalResult => {
           //do whatever the 'callback' would do
       })
       .catch((err) => {
           //do whatever the error handler needs
       });
};

为了做到这些,代码中使用到的函数必须Promise化。我们看一下getRoles如何更新的返回一个Promise对象的。

const getRoles = function (username){
   return new Promise((resolve, reject) => {
       database.connect((connection) => {
           connection.query('get roles sql', (result) => {
               resolve(result);
           })
       });
   });
};

我们将此方法修改为返回一个Promise对象,此对象需要传入两个回调函数,并且在Promise对象中执行操作。现在,resolvereject两个回调函数会分别映射为Promise.thenPromise.catch

你可能意识到getRoles方法的内部仍然是可能遭受地狱回调的,因为database的方法并没有返回一个Promise对象。如果database的连接方法也返回一个Promise对象,那么getRoles就会像下面这样

const getRoles = new function (userInfo) {
   return new Promise((resolve, reject) => {
       database.connect()
           .then((connection) => connection.query('get roles sql'))
           .then((result) => resolve(result))
           .catch(reject)
   });
};

方式3:Async/Await

JavaScript默认是异步的。这也可能是为什么JavaScript花了很长时间才使得代码看起来像同步的。但是,迟到了总比没有强。地狱回调在引入了Promise对象之后改善了很多。但是我们仍然需要传递回调函数给Promise对象.then.catch

Promise给JavaScript带来了最酷之一的改变。ECMAScript 2017在Promise之上已async和await表达式的方式带来了语法糖。这些使得我们可以基于Promise的代码看起来不像是异步的一样,并且也不会阻塞主线程。

代码如下:

const verifyUser = async function(username, password){
   try {
       const userInfo = await dataBase.verifyUser(username, password);
       const rolesInfo = await dataBase.getRoles(userInfo);
       const logStatus = await dataBase.logAccess(userInfo);
       return userInfo;
   }catch (e){
       //handle errors as needed
   }
};

await promise的操作仅仅允许在async修饰的函数中使用,async修饰的函数表示verifyUser必须定义为异步函数

然而,这些简单的改变就可以await任何Promise,并且不需要修改任何代码。并且异步代码可以想同步代码一样愉快的编码了。

Async 一个等待已久的Promise的进步

异步函数是JavaScript中异步编程的下一个里程碑。他们会使得代码更为干净并且更为简单的维护。声明一个函数为async会确保函数返回的是一个Promise对象(即不需要单独的定义返回的对象为一个Promise对象),所以你并不需要为此再担心了。

我们如今为什么需要使用JavaScript中的async函数?

  1. 代码会更为整洁
  2. 异常处理更为简单,仅仅像在其他异步代码使用try/catch即可
  3. 调试更为简单。在.then代码块设置断点不会移动到下一个断点,.then只会执行同步代码。但是调试async函数就和调试同步代码一样

本文翻译自https://blog.hellojs.org/asynchronous-javascript-from-callback-hell-to-async-and-await-9b9ceb63c8e8

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值