异步JavaScript:从回调地狱到异步和等待

前言

上周由于工作忙和周末头疼,最终没能发布这篇。现在抓紧时间弥补上。

原文Asynchronous JavaScript: From Callback Hell to Async and Await 作者:DEMIR SELMANOVIC 

正文

编写一个成功的Web应用程序的关键之一是能够每页打几十个AJAX调用。

这是一个典型的异步编程挑战,您如何选择处理异步调用,在很大程度上,会导致或破坏您的应用程序,并且可能是您的整个启动。

在很长一段时间内,在JavaScript中同步异步任务是一个严重的问题。

这个挑战正在影响使用Node.js的后端开发人员以及使用任何JavaScript框架的前端开发人员。异步编程是我们日常工作的一部分,但是这个挑战经常被忽略,而不是在正确的时间考虑。

异步JavaScript简史

第一个也是最直接的解决方案是以嵌套函数的形式作为回调。这个解决方案导致了所谓的回调地狱,而且太多的应用程序仍然感到它的燃烧。

然后,我们有了Promises。这种模式使代码更容易阅读,但与“不要重复自己”(DRY)原则相去甚远。仍然有太多的情况下,你不得不重复相同的代码段来正确管理应用程序的流程。async / await语句形式的最新补充最终使JavaScript中的异步代码像其他任何代码一样易于读写。

让我们来看看每个解决方案的例子,并反思JavaScript中异步编程的发展。

为此,我们将检查执行以下步骤的简单任务:

  1. 验证用户的用户名和密码。
  2. 获取用户的应用程序角色。
  3. 记录用户的应用程序访问时间。

方法1:回调地狱(“末日金字塔”)

对这些调用进行同步的古老解决方案是通过嵌套回调。对于简单的异步JavaScript任务来说,这是一种不错的方法,但是由于一个名为回调地狱的问题而无法扩展。

三个简单任务的代码如下所示:

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原则在这种情况下完全没有价值。例如,在每个函数中重复错误处理,并且从每个嵌套函数调用主回调。

更复杂的异步JavaScript操作(例如通过异步调用进行循环)是一个更大的挑战。事实上,用回调来做这件事并不是一件容易的事情。这就是为什么像蓝鸟Q这样的JavaScript Promise库获得如此多的关注。它们提供了一种对语言本身尚未提供的异步请求执行常见操作的方法。

这就是原生JavaScript Promises 进来的原因。

JavaScript Promises

Promises是逃避回调地狱的下一个合乎逻辑的步骤。这个方法并没有去掉回调函数的使用,但是它使得函数的链接简单明了,简化了代码,使得它更容易阅读。

有了Promise,我们的异步JavaScript示例中的代码将如下所示:

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

为了达到这种简单性,例子中使用的所有函数都必须是Promisified的。让我们来看看如何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方法仍然是内部倾向于厄运现象的金字塔。这是由于数据库方法的创建方式,因为它们不会返回Promise。如果我们的数据库访问方法也返回,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中正确运行的同步代码的原因。但是,迟到比从未更好!厄运的引入极大地缓解了厄运的金字塔。不过,我们仍然需要依靠传递给的回调函数.then.catch方法Promise

承诺为JavaScript中最酷的改进之一铺平了道路。ECMAScript 2017在JavaScript中以Promises的形式asyncawait语句引入了语法糖。

它们允许我们编写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
   }
};

等待Promise解决只允许在必须定义的async函数内verifyUser使用async function

然而,一旦这种小的变化是由你可以await任何Promise没有其他方法的其他变化。

Async  - 一个长期的等待解决方案的Promise

异步函数是JavaScript中异步编程发展的下一个合理步骤。他们将使您的代码更清洁,更容易维护。声明一个函数async将确保它总是返回一个,Promise所以你不必担心这个问题了。

为什么你async今天要开始使用JavaScript 函数?

  1. 由此产生的代码更清洁。
  2. 错误处理要简单得多,它依赖于trycatch就像在其他同步代码中一样。
  3. 调试要简单得多。在.then块内设置断点不会移动到下一个,.then因为它只能通过同步代码。但是,您可以await像呼叫同步一样通过呼叫。

了解基础知识

什么是 async和await?

Async/await语句是在JavaScript Promises之上创建的语法糖。它们允许我们编写基于Promise的代码,就好像它是同步的,但不阻塞主线程。

什么是回调地狱?

在JavaScript中,回调地狱是代码中的一种反模式,这是由于异步代码结构不良造成的。当程序员尝试在基于异步回调的JavaScript代码中强制使用可视化的自顶向下结构时,通常会看到这种情况。

什么是JavaScript promises?

JavaScript中的promise就像一个占位符值,预期最终将解决最终成功的结果值或失败的原因。

关于作者: Demir是一名开发人员和项目经理,在广泛的软件开发角色方面拥有超过15年的专业经验。他擅长作为独立开发人员,团队成员,团队负责人或多个分布式团队的经理。他与客户紧密合作,确定想法并交付产品。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏码农分享

3.1、苏宁百万级商品爬取 思路讲解 商品爬取

https://ds.suning.cn/ds/generalForTile/000000010044087492_,000000000688241235_,0...

2272
来自专栏Kirito的技术分享

JAVA 拾遗--JPA 二三事

记得前几个月,spring4all 社区刚搞过一次技术话题讨论:如何对 JPA 或者 MyBatis 进行技术选型?传送门:http://www.spring4...

40310
来自专栏牛客网

51信用卡前端凉面

1650
来自专栏互联网杂技

ES2017 异步函数现已正式可用

ES2017标准已于2017年6月份正式定稿了,并广泛支持最新的特性:异步函数。如果你曾经被异步 JavaScript 的逻辑困扰,这么新函数正是为你设计的。 ...

2844
来自专栏京东技术

京东资深架构师代码评审歪诗

1823
来自专栏深度学习自然语言处理

【收藏】这些Python代码技巧,你肯定还不知道

人们还经常把 Python 笑称为「可执行伪码(executable pseudocode)」。但是,当你可以编写这样的代码时,很难去反驳这种言论:

713
来自专栏机器之心

这些Python代码技巧,你肯定还不知道

人们还经常把 Python 笑称为「可执行伪码(executable pseudocode)」。但是,当你可以编写这样的代码时,很难去反驳这种言论:

1153
来自专栏有趣的Python和你

千里之行,始于足下变量字符串

983
来自专栏星汉技术

Scala简介和安装

2976
来自专栏牛客网

热乎乎的阿里面经,攒人品

一面: 1.自我介绍一下,然后问你最擅长哪一方面(我说虚拟机、集合) 2.他说HashMap我现在都不敢问了,出过很多笑话,很多人都是背的,自己没办法判断是看...

3485

扫码关注云+社区