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

本文概述

  • 异步JavaScript的简要历史
  • 方法1:回调地狱(“ 厄运金字塔” )
  • JavaScript承诺
  • 方法3:异步/等待
  • 异步-期待已久的承诺解决方案
编写成功的Web应用程序的关键之一是每页可以进行数十次AJAX调用。
这是一个典型的异步编程挑战, 选择如何处理异步调用将在很大程度上破坏或破坏你的应用程序, 并可能扩展整个启动过程。
长时间以来, 在JavaScript中同步异步任务一直是一个严重的问题。
这一挑战对使用Node.js的后端开发人员的影响与使用任何JavaScript框架的前端开发人员的影响一样大。异步编程是我们日常工作的一部分, 但是挑战常常被轻视, 而不是在正确的时间考虑。
异步JavaScript的简要历史 第一个也是最直接的解决方案是以嵌套函数的形式作为回调。这种解决方案导致了一种称为” 回调地狱” 的事情, 但是仍有太多的应用程序感到它被烧死了。
然后, 我们得到了承诺。这种模式使代码更易于阅读, 但与” 不要重复自己做” (DRY)原理相去甚远。在很多情况下, 你必须重复相同的代码来正确管理应用程序的流程。最新的添加形式(以async / await语句的形式)最终使JavaScript中的异步代码像其他任何代码一样易于读写。
让我们看一下每种解决方案的示例, 并反思JavaScript中异步编程的发展。
为此, 我们将检查一个简单的任务, 该任务执行以下步骤:
  1. 验证用户名和密码。
  2. 获取用户的应用程序角色。
  3. 记录用户的应用程序访问时间。
方法1:回调地狱(“ 厄运金字塔” ) 同步这些调用的古老方法是通过嵌套回调。对于简单的异步JavaScript任务, 这是一种不错的方法, 但由于存在回调地狱之类的问题, 因此无法扩展。
异步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操作(例如遍历异步调用)是一个更大的挑战。实际上, 使用回调没有简单的方法。这就是为什么JavaScript Promise库(例如Bluebird和Q)获得如此高的吸引力的原因。它们提供了一种对语言本身尚未提供的异步请求执行通用操作的方法。
这就是原生JavaScript Promises出现的地方。
JavaScript承诺 承诺是逃避回调地狱的下一个逻辑步骤。此方法没有删除回调的使用, 但使函数链接变得简单明了, 并简化了代码, 使其更易于阅读。
异步JavaScript(从回调地狱到异步和等待)

文章图片
有了Promises后, 异步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 }); };

为了实现这种简单性, 示例中使用的所有功能都必须被承诺。让我们看一下如何更新getRoles方法以返回Promise:
const getRoles = function (username){ return new Promise((resolve, reject) => { database.connect((connection) => { connection.query('get roles sql', (result) => { resolve(result); }) }); }); };

我们已经修改了该方法以返回带有两个回调的Promise, 并且Promise本身执行该方法中的操作。现在, 解决和拒绝回调将分别映射到Promise.then和Promise.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:异步/等待 承诺金字塔的引入极大地缓解了厄运金字塔。但是, 我们仍然必须依靠传递给Promise的.then和.catch方法的回调。
承诺为JavaScript的最酷的改进之一铺平了道路。 ECMAScript 2017以async和await语句的形式在JavaScript的Promises之上引入了语法糖。
它们使我们能够编写基于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解决, 这意味着必须使用异步函数定义verifyUser。
但是, 一旦进行了此小更改, 你就可以等待任何Promise, 而无需其他方法进行其他更改。
异步-期待已久的承诺解决方案 异步功能是JavaScript异步编程发展的下一个逻辑步骤。它们将使你的代码更简洁, 更易于维护。将函数声明为异步将确保其始终返回Promise, 因此你不必再为此担心。
【异步JavaScript(从回调地狱到异步和等待)】今天为什么要开始使用JavaScript异步功能?
  1. 生成的代码更加简洁。
  2. 错误处理要简单得多, 并且像其他任何同步代码一样, 它也依赖于try / catch。
  3. 调试要简单得多。在.then块内设置断点将不会移至下一个.then, 因为它只会逐步执行同步代码。但是, 你可以逐步执行等待呼叫, 就好像它们是同步呼叫一样。

    推荐阅读