作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Demir Selmanovic
Verified Expert in Engineering
24 Years of Experience

Demir是一名开发人员和项目经理,在广泛的软件开发角色方面拥有超过15年的专业经验.

Share

编写成功的web应用程序的关键之一是能够在每个页面上进行数十次AJAX调用.

This is a typical asynchronous programming challenge, and how you choose to deal with asynchronous calls will, in large part, make or break your app, and by extension potentially your entire startup.

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

This challenge is affecting back-end developers using Node.js 与使用任何JavaScript框架的前端开发人员一样多. Asynchronous programming is a part of our everyday work, 但这一挑战往往被轻视,没有在适当的时候加以考虑.

A Brief History of Asynchronous JavaScript

第一个也是最直接的解决方案是 nested functions as callbacks. This solution led to something called callback hell, and too many applications still feel the burn of it.

Then, we got Promises. This pattern made the code a lot easier to read, 但这与“不要重复自己”(DRY)原则相去甚远. 仍然有很多情况下,您必须重复相同的代码片段才能正确地管理应用程序的流. 最新的添加,以async/await JavaScript语句的形式,终于完成了 asynchronous code in JavaScript as easy to read and write as any other piece of code.

让我们看一下这些解决方案的示例,并回顾一下JavaScript中异步编程的发展历程.

To do this, 我们的异步JavaScript教程将研究一个执行以下步骤的简单任务:

  1. Verify the username and password of a user.
  2. Get application roles for the user.
  3. Log application access time for the user.

Approach 1: Callback Hell (“The Pyramid of Doom”)

同步这些调用的古老解决方案是通过嵌套回调. 对于简单的异步JavaScript任务来说,这是一种不错的方法, but wouldn’t scale because of an issue called callback hell.

插图:异步JavaScript回调地狱反模式

这三个简单任务的代码看起来像这样:

const verifyUser =函数(用户名,密码,回调){
   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);
                       }
                   })
               }
           })
       }
   })
};

每个函数都有一个参数,该参数是另一个函数,该函数被调用时带有一个参数,该参数是前一个操作的响应.

太多的人仅仅通过阅读上面的句子就会经历大脑冻结. 拥有数百个类似代码块的应用程序将给维护代码的人员带来更多麻烦, even if they wrote it themselves.

这个例子会变得更加复杂 database.getRoles is another function that has nested callbacks.

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

除了代码难以维护之外, the DRY principle has absolutely no value in this case. Error handling, for example, 在每个函数中重复,并且从每个嵌套函数调用主回调.

More complex asynchronous JavaScript operations, such as looping through asynchronous calls, is an even bigger challenge. 实际上,没有简单的方法可以通过回调来实现这一点. This is why JavaScript Promise libraries like Bluebird and Q got so much traction. 它们提供了一种对异步请求执行通用操作的方法,这是语言本身没有提供的.

That’s where native JavaScript Promises come in.

JavaScript Promises

Promises were the next logical step in escaping callback hell. This method did not remove the use of callbacks, 但是它使JavaScript中异步函数的链接变得简单明了 simplified the code, making it much easier to read.

Illustration: Asynchronous JavaScript Promises diagram

With Promises in place, 我们的异步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. Let’s take a look at how the getRoles method would be updated to return a Promise:

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

We have modified the method to return a Promise, with two callbacks, and the Promise itself performs actions from the method. Now, resolve and reject callbacks will be mapped to Promise.then and Promise.catch methods respectively.

You may notice that the getRoles 方法内部仍容易出现金字塔厄运现象. 这是由于数据库方法的创建方式,因为它们不返回 Promise. If our database access methods also returned Promise the getRoles method would look like the following:

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

Approach 3: Async/Await

随着“承诺”的引入,厄运金字塔得到了显著的缓解. 然而,我们仍然必须依赖传递给的回调 .then and .catch methods of a Promise.

承诺为JavaScript中最酷的改进之一铺平了道路. ECMAScript 2017 在JavaScript的Promises之上添加了语法糖,形式是 async and await statements.

They allow us to write Promise-based code as if it were synchronous, but without blocking the main thread, as this code sample demonstrates:

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

Awaiting Promise to resolve is allowed only within async functions which means that verifyUser had to be defined using async function.

However, once this small change is made you can await any Promise without additional changes in other methods.

Async JavaScript——期待已久的承诺解决方案

异步函数是JavaScript中异步编程发展的下一个合乎逻辑的步骤. 它们将使您的代码更干净,更易于维护. Declaring a function as async will ensure that it always returns a Promise so you don’t have to worry about that anymore.

What does async 为什么你应该开始使用JavaScript async functions today?

  1. The resulting code is much cleaner.
  2. Error handling is much simpler and it relies on try/catch just like in any other synchronous code.
  3. Debugging is much simpler. Setting a breakpoint inside a .then block will not move to the next .then because it only steps through synchronous code. But, you can step through await calls as if they were synchronous calls.

Understanding the basics

  • What are async and await?

    Async/await语句是在JavaScript Promises之上创建的语法糖. 它们允许我们编写基于承诺的代码,就好像它是同步的一样, but without blocking the main thread.

  • What is callback hell?

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

  • What are JavaScript promises?

    JavaScript中的promise就像一个占位符值,期望最终解析为最终成功的结果值或失败的原因.

Hire a Toptal expert on this topic.
Hire Now
Demir Selmanovic

Demir Selmanovic

Verified Expert in Engineering
24 Years of Experience

萨拉热窝,波斯尼亚-黑塞哥维那联邦,波斯尼亚-黑塞哥维那

Member since July 8, 2014

About the author

Demir是一名开发人员和项目经理,在广泛的软件开发角色方面拥有超过15年的专业经验.

作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

World-class articles, delivered weekly.

By entering your email, you are agreeing to our privacy policy.

World-class articles, delivered weekly.

By entering your email, you are agreeing to our privacy policy.

Toptal Developers

Join the Toptal® community.