原文链接:https://blog.patricktriest.com/what-is-async-await-why-should-you-care/
复制代码

停止书写回调函数并爱上ES8

以前,JavaScript项目会逐渐‘失去控制’,其中主要一个原因就是采用传统的回调函数处理异步任务时,一旦业务逻辑比较复杂,我们就难免书写一些冗长、复杂、嵌套的代码块(回调地狱),这会严重降低代码的可读性与可维护性。现在,JavaScript提供了一种新的语法糖来取代回调函数,使我们能够编写简明、可读性高的异步代码。

背景

AJAX

先来回顾一下历史。在20世纪90年代后期,Ajax是异步JavaScript的第一个重大突破。这一技术允许网站在加载HTML后获取并显示最新的数据,这是一个革命性的想法。在这之前,大多数网站会再次下载整个页面来显示更新的内容。这一技术(在jQuery中以ajax的名称流行)主导了2000-2010的web开发并且Ajax是目前网站用来获取数据的主要技术,但是XML在很大程度上取代了JSON。

NodeJS

当NodeJS在2009年首次发布时,服务器端环境的主要焦点是允许程序优雅地处理并发性。大多数服务器端语言通过阻塞代码来处理I/O操作,直到操作完成为止。相反,NodeJS使用的是事件循环机制,这样开发人员可以在非阻塞异步操作完成后,调用回调函数来处理逻辑(类似于Ajax的工作方式)。

Promises

几年后,NodeJS和浏览器环境中出现了一种新的标准,称为"Promise",Promise提供了一种强大的、标准化的方式来组成异步操作。Promise仍然使用基于回调的格式,但为链式和组合异步操作提供了一致的语法。在2015年,由流行的开源库所倡导的Promise最终被添加为JavaScript的原生特性。 Promise是一个不错的改进,但它们仍然常常是一些冗长而难以阅读的代码块的原因。 而现在有了一个解决方案。 Async/Await是一种新的语法(从.net和C#中借用),它允许我们编写Promise,但它们看起来像是同步代码,没有回调,可以用来简化几乎任何现有的JS应用程序。Async/Await是JavaScript语言的新增的特性,在ES7中被正式添加为JavaScript的原生特性。

示例

我们将通过一些代码示例来展示Async/Await的魅力

:运行下面的示例不需要任何库。Async/Await已经被最新版本的Chrome、FireFox、Safari、Edge完全支持,你可以在你的浏览器控制台里运行例子。Async/Await需要运行在NodeJS 7.6版本及以上,同时也被Babel、TypeScript转译器支持。所以Async/Await可以被用于实际开发之中。

准备

我们会使用一个虚拟的API类,你也可以在你的电脑上运行。这个类通过返回promise来模拟异步请求。正常情况下,promise被调用后,200ms后会对数据进行处理。

class Api {constructor () {this.user = { id: 1, name: 'test' }this.friends = [ this.user, this.user, this.user ]this.photo = 'not a real photo'}getUser () {return new Promise((resolve, reject) => {setTimeout(() => resolve(this.user), 200)})}getFriends (userId) {return new Promise((resolve, reject) => {setTimeout(() => resolve(this.friends.slice()), 200)})}getPhoto (userId) {return new Promise((resolve, reject) => {setTimeout(() => resolve(this.photo), 200)})}throwError () {return new Promise((resolve, reject) => {setTimeout(() => reject(new Error('Intentional Error')), 200)})}
}
复制代码

每个示例依次执行如下三个操作: 获取一个用户的信息,获取该用户的朋友, 获取该用户的照片。在最后,我们会在控制台中打印这些结果。

方法一 --- Nested Promise Callback Functions

使用嵌套的promise回调函数

function callbackHell () {const api = new Api()let user, friendsapi.getUser().then(function (returnedUser) {user = returnedUserapi.getFriends(user.id).then(function (returnedFriends) {friends = returnedFriendsapi.getPhoto(user.id).then(function (photo) {console.log('callbackHell', { user, friends, photo })})})})
}
复制代码

对于任何一个从事过JavaScript项目开发的人来说,这个代码块非常熟悉。非常简单的业务逻辑,但是代码却是冗长、深嵌套,并且以这个结尾.....

     })})})
}
复制代码

在真实的业务场景中,每个回调函数可能更复杂,代码块会以一堆充满层次感的})为结尾。“回调函数里面嵌套着回调函数嵌套着回调函数”,这就是被传说中的“回调地狱”(“回调地狱”的诞生不只是因为代码块的混乱,也源于信任问题。)。 更糟糕的是,我们为了简化,还没有做错误处理机制,如果加上了reject......细思极恐

方法二 --- Promise Chain

让我们优雅起来

function promiseChain () {const api = new Api()let user, friendsapi.getUser().then((returnedUser) => {user = returnedUserreturn api.getFriends(user.id)}).then((returnedFriends) => {friends = returnedFriendsreturn api.getPhoto(user.id)}).then((photo) => {console.log('promiseChain', { user, friends, photo })})
}
复制代码

Promise有一个很棒的特性:Promise.prototype.then()和Promise.prototype.catch()返回Promise对象,这就使得我们可以将这些promise连接成一个promise链。通过这种方法,我们可以将这些回调函数放在一个缩进层次里。与此同时,我们使用了箭头函数简化了回调函数声明。 对比之前的回调地狱,使用promise链使得代码的可读性大大提高并且拥有着更好的序列感,但是看起来还是非常冗长并且有一点复杂。

方法三 --- Async/Await

我们可不可以不写回调函数?就写7行代码能解决吗?

async function asyncAwaitIsYourNewBestFriend () {const api = new Api()const user = await api.getUser()const friends = await api.getFriends(user.id)const photo = await api.getPhoto(user.id)console.log('asyncAwaitIsYourNewBestFriend', { user, friends, photo })
}
复制代码

优雅多了,调用await之前我们会一直等待,直到promise被决议并将值赋值给左边的变量。通过async/await,我们可以对异步操作流程进行控制,就好像它是同步代码。

注:await必须搭配async一起使用,注意上面的函数,我们将关键字async放在了函数的声明前,这是必需的。稍后,我们会深入讨论这个问题

循环

Async/Await可以让以前很多复杂的代码变得简明。举个例子,如果我们要按序检索每个用户的朋友的朋友列表。

方法一 --- Recursive Promise Loop

下面是使用传统的promise来按序获取每个朋友的朋友列表

function promiseLoops () {  const api = new Api()api.getUser().then((user) => {return api.getFriends(user.id)}).then((returnedFriends) => {const getFriendsOfFriends = (friends) => {if (friends.length > 0) {let friend = friends.pop()return api.getFriends(friend.id).then((moreFriends) => {console.log('promiseLoops', moreFriends)return getFriendsOfFriends(friends)})}}return getFriendsOfFriends(returnedFriends)})
}
复制代码

我们创建在promiseLoops中创建了一个函数用于递归地去获取朋友的朋友列表。这个函数体现了函数式编程,但是对于这个简单的任务而言,这依旧是一个比较复杂的解决方案。

方法二 --- Async/Await For-Loop

让我们尝试一下Async/Await

async function asyncAwaitLoops () {const api = new Api()const user = await api.getUser()const friends = await api.getFriends(user.id)for (let friend of friends) {let moreFriends = await api.getFriends(friend.id)console.log('asyncAwaitLoops', moreFriends)}
}
复制代码

不需要写递归promise闭包,只需要使用一个for循环就能解决我们的问题。

并行

一个一个地去获取朋友的朋友的列表看起来有点慢,为什么不并行处理请求呢?我们可以用async/await来处理并行任务吗? 当然

async function asyncAwaitLoopsParallel () {const api = new Api()const user = await api.getUser()const friends = await api.getFriends(user.id)const friendPromises = friends.map(friend => api.getFriends(friend.id))const moreFriends = await Promise.all(friendPromises)console.log('asyncAwaitLoopsParallel', moreFriends)
}
复制代码

为了并行请求,我们使用了一个promise数组并将它传递给方法Promise.all(),Promise.all()会返回一个promise,一旦所有的请求完成就会决议。

错误处理

然而,在异步编程中有一个主要的问题还没解决:错误处理。在异步操作中,我们必须为每个操作编写单独的错误处理回调,在调用栈的顶部去找出正确的报错位置可能很复杂,所以我们得在每个回调开始时就去检查是否抛出了错误。所以,引入错误处理后的回调函数会比之前复杂度成倍增加,如果没有主动定位到报错的位置,这些错误甚至会被“吞掉”。 现在,我们给之前的例子添上错误处理机制。为了测试错误处理机制,我们将在真正获取到用户图片之前使用抽象类里的api.throwError()方法。

方法一 --- Promise Error Callbacks

让我们看看最坏的情况

function callbackErrorHell () {const api = new Api()let user, friendsapi.getUser().then(function (returnedUser) {user = returnedUserapi.getFriends(user.id).then(function (returnedFriends) {friends = returnedFriendsapi.throwError().then(function () {console.log('Error was not thrown')api.getPhoto(user.id).then(function (photo) {console.log('callbackErrorHell', { user, friends, photo })}, function (err) {console.error(err)})}, function (err) {console.error(err)})}, function (err) {console.error(err)})}, function (err) {console.error(err)})
}
复制代码

代码除了又长又丑陋以外,代码操作流也不直观,不像同步、可读性高的代码那样从上往下。

方法二 --- Promise Chain "Catch" Method

我们可以给promise链添加catch方法来改善一些

function callbackErrorPromiseChain () {const api = new Api()let user, friendsapi.getUser().then((returnedUser) => {user = returnedUserreturn api.getFriends(user.id)}).then((returnedFriends) => {friends = returnedFriendsreturn api.throwError()}).then(() => {console.log('Error was not thrown')return api.getPhoto(user.id)}).then((photo) => {console.log('callbackErrorPromiseChain', { user, friends, photo })}).catch((err) => {console.error(err)})
}
复制代码

看起来好多了,我们通过给promise添加一个错误处理取代了之前给每个回调函数添加错误处理。但是,这还是有一点复杂并且我们还是需要使用一个特殊的回调来处理异步错误而不是像对待正常的JavaScript错误那样处理它们。

方法三 --- Normal Try/Catch Block

我们可以做得更好

async function aysncAwaitTryCatch () {try {const api = new Api()const user = await api.getUser()const friends = await api.getFriends(user.id)await api.throwError()console.log('Error was not thrown')const photo = await api.getPhoto(user.id)console.log('async/await', { user, friends, photo })} catch (err) {console.error(err)}
}
复制代码

我们将异步操作放进了处理同步代码的try/catch代码块。通过这种方法,我们完全可以像对待同步代码的一样处理异步代码的错误。代码看起来非常简明

组合

我在前面提及了任何以async的函数可以返回一个promise。这使得我们可以真正轻松地组合异步控制流 举个例子,我们可以重新整理前面的例子,将获取数据和处理数据分开。这样我们就可以通过调用async函数获取数据。

async function getUserInfo () {const api = new Api()const user = await api.getUser()const friends = await api.getFriends(user.id)const photo = await api.getPhoto(user.id)return { user, friends, photo }
}function promiseUserInfo () {getUserInfo().then(({ user, friends, photo }) => {console.log('promiseUserInfo', { user, friends, photo })})
}
复制代码

更棒的是,我们可以在数据接受函数里使用async/await,这将使得整个异步模块更加明显。 如果我们要获取前面10个用户的数据呢?

async function getLotsOfUserData () {const users = []while (users.length < 10) {users.push(await getUserInfo())}console.log('getLotsOfUserData', users)
}
复制代码

并发呢?并且加上错误处理呢?

async function getLotsOfUserDataFaster () {try {const userPromises = Array(10).fill(getUserInfo())const users = await Promise.all(userPromises)console.log('getLotsOfUserDataFaster', users)} catch (err) {console.error(err)}
}
复制代码

结论

随着SPA的兴起和NodeJS的广泛应用,对于JavaScript开发人员来说,优雅地处理并发性比以往任何时候都要重要。Async/Await缓解了许多因为bug引起且已经影响JavaScript很多年的控制流问题,并且使得代码更加优雅。如今,主流的浏览器和NodeJS都已经支持了这些语法糖,所以现在是使用Async/Await的最好时机。

使用 Async / Await 来编写简明的异步代码相关推荐

  1. TypeSrcript如何引入第三方库 如果加d.ts以及async await如何使用 demo,只有代码,文字后续补充...

    https://files.cnblogs.com/files/cappuccino/laya2.rar

  2. JavaScript是如何工作的:事件循环和异步编程的崛起+ 5种使用 async/await 更好地编码方式!...

    此篇是 JavaScript是如何工作的第四篇,其它三篇可以看这里: JavaScript是如何工作的:引擎,运行时和调用堆栈的概述! JavaScript是如何工作的:深入V8引擎&编写优化 ...

  3. JavaScript 如何工作的: 事件循环和异步编程的崛起 + 5 个关于如何使用 async/await 编写更好的技巧...

    原文地址:How JavaScript works: Event loop and the rise of Async programming + 5 ways to better coding wi ...

  4. [译] JavaScript 如何工作的: 事件循环和异步编程的崛起 + 5 个关于如何使用 async/await 编写更好的技巧...

    原文地址:How JavaScript works: Event loop and the rise of Async programming + 5 ways to better coding wi ...

  5. ES2017 异步函数async/await

    ES2017标准已于2017年6月份正式定稿了,并广泛支持最新的特性:异步函数.如果你曾经被异步 JavaScript 的逻辑困扰,这么新函数正是为你设计的. 异步函数或多或少会让你编写一些顺序的 J ...

  6. Python 异步 IO 、协程、asyncio、async/await、aiohttp

    From :廖雪峰 异步IO :https://www.liaoxuefeng.com/wiki/1016959663602400/1017959540289152 Python Async/Awai ...

  7. Atitit. Async await 优缺点 异步编程的原理and实现 java c# php

    Atitit. Async await 优缺点 异步编程的原理and实现 java c# php 1. async & await的来源1 2. 异步编程history1 2.1. 线程池 2 ...

  8. 解决异步问题,教你如何写出优雅的promise和async/await,告别callback回调地狱!

    解决异步问题--promise.async/await 一.单线程和异步 1.单线程是什么 2.为什么需要异步 3.使用异步的场景 二.promise 1.promise的三种状态 2.三种状态的表现 ...

  9. 明明有了 promise ,为啥还需要 async await ?

    作者 | Angus安格斯 来源 | https://juejin.cn/post/6960855679208783903 为了让还没听说过这个特性的小伙伴们有一个大致了解,以下是一些关于该特性的简要 ...

最新文章

  1. R语言构建混淆矩阵(仿真数据)并基于混淆矩阵(confusion matrix)计算并计算Accuracy、Precision、Recall(sensitivity)、F1、Specificity指标
  2. Java 批量文件不打包下载_【Java】Java批量文件打包下载zip
  3. 虚拟手柄控制的小车 air3.4 Android IPones4s 下运行正常
  4. 自学python需要多长时间-自学Python要学多久可以学会?
  5. linux wifi关闭5g,TP-Link路由器如何关闭5G无线Wi-Fi信号?
  6. 使用python读取txt坐标文件生成挖空矿山_探矿批量
  7. open source protocols
  8. 出现画面抖动_连续抖动20小时!虎门大桥桥面如波浪翻滚,专家:个人感觉没问题...
  9. TensorFlow 教程 --教程--2.10偏微分方程
  10. 记一次无法登录 wine QQ
  11. #include #import @class 讲解
  12. 【Gbase】建表时候hash分布列的制定方式(DISTRIBUTED BY column_name)
  13. Oracle数据库链接源文件
  14. 解决sql2005远程连接报错,提示请验证实例名称是否正确并且 SQL Server 已配置为允许远程连接
  15. 网页自动关机代码HTML,自动定时关机命令
  16. mysql 建表语句
  17. python安装pyqt4_如何使用pip在Windows上安装PyQt4?
  18. php输入指定文字转换成图片的简单例子
  19. flutter web 微信授权和微信支付
  20. web前端之贪吃蛇网页版小游戏

热门文章

  1. svn上传时显示database is locked
  2. angularjs跨页面传参遇到的一些问题
  3. 看完性能简报,想不优化好都难!
  4. 《天风文章》 V1.1.0设计文档
  5. 如何不让你的APP在模拟器中运行。
  6. .Net/C# 实现真正的只读的 Hashtable 类型的属性 (ReadOnly Hashtable Property)
  7. mysql批量更新报错_Mysql批量更新的三种方式
  8. 微信公众帐号开发教程第8篇-文本消息中使用网页超链接
  9. 使用axis2解析wsdl反向生成webservice客户端
  10. struts2.xml中使用chain和redirectAction这两个注意事项