前言

异步编程一直是 JavaScript 中比较麻烦但相当重要的一件事情,一直也有人在提出各种方案,试图解决这个问题。

从回调函数到 Promise 对象,再到 Generator 函数,每次都有所改进,但都不彻底,直到出现了 async 函数,很多人认为它是异步操作的终极解决方案。

但很多人对于async 和 await 的原理却一知半解,只知道可以解决异步问题,知其然,不知其所以然。所以,本篇文章对于async、await 的原理进行详细解释,希望可以帮到大家。有疑问,欢迎留言评论。

1. async await 是什么

async、await 是 ES8(ECMAScript 2017)引入的新语法,用来简化 Promise 异步操作。

async 是 “异步”的简写,await 可以认为是 async await 的简写

async 用来声明一个 function 是异步的,await 用来等待一个异步方法执行完成。

有个规定:await 只能出现在 async 函数中

2. async

首先,先了解 async 函数返回的是什么?以下面代码为例

看到这里,就会发现,async 声明的函数,返回的结果是一个 Promise 对象。如果在函数中 return 一个直接量,async 会把这个直接量通过 Promise.resolve() 封装成 Promise 对象。

补充:

Promise.resolve(x) 等价于 new Promise(resolve => resolve(x)),用于快速封装字面量对象或其他对象,将其封装成 Promise 实例。

如果 async 函数没有返回值。

通过一个简单的例子区分 async 关键字函数 和 普通函数的区别

async function fn1(){return 123
}function fn2(){return 123
}
console.log(fn1())
console.log(fn2())//输出结果为:
// Promise {<fulfilled>: 123}
// 123

从这里我们可以看出来,带有 async 关键字的函数,相比较普通函数,无非是把返回值包装了下。

注意

  • async 表示函数内部有异步操作
  • await 关键字要写在 async 关键字函数的内部,写在外面会报错。

3. await

一般来说,认为 await 在等待一个 async 函数完成。不过按照语法来看,await 等待的是一个表达式。这个表达式的计算结果是 Promise 对象或者其他值。

因为 async 函数返回的是一个 Promise 对象,所以 await 可以用于等待一个 async 函数的返回值—这也可以说是 await 在等 async 函数。但要清楚,**它等的实际上是一个返回值。**注意到 await 不仅仅用于等 Promise 对象,它可以等任何表达式的结果。所以,await 后面是可以接普通函数调用或者直接量的。

一句话:await 等待的是右侧表达式的返回值!!!

比如下面的例子

function getData() {return '哈哈哈'
}async function testAsync() {return Promise.resolve('hello async')
}
async function test() {const v1 = await getData()const v2 = await testAsync()console.log(v1)console.log(v2)
}test()// 输出的结果为:
// 哈哈哈
// hello async
  • 如果 await 等到的不是一个 Promise 对象,那么 await 表达式的运算结果就是它等到的东西,比如返回的是字符串,那么运算结果就是字符串
  • 如果 await 等到的是一个 Promise 对象,await 就开始忙起来,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。

看到上面的 “阻塞”,不要慌,这就是 await 必须用在 async 函数中的原因。async 调用不会造成阻塞,它内部所有的阻塞都被封装在一个 Promise 中,而 await 会等待 这个 Promise 完成,并将其 resolve 的结果返回出来。

4. async / await 的优势

单一的 Promise 链并不能发现 async/await 的优势,真正能体现出其优势的是其处理多个 Promise 组成的 then 链的时候(Promise 通过 then 链来解决多层回调的问题,现在又需要 async/await 来进一步优化它)。

假设一个逻辑,分多个步骤完成,每一个步骤都是异步的,并且依赖于上一个步骤的结果。(简单来说,每一个异步都要按指定的顺序来了执行)我们用 setTimeout 来进行一次模拟异步操作。

/**
传入参数 n,表示这个函数执行的时间(毫秒)
执行的结果是 n + 200,这个值将用于下一步骤
*/function getData(n) {return new Promise(resolve => {setTimeout(() => resolve(n + 200), n)})
}function step1(n) {console.log(`step1 with ${n}`)return getData(n)
}function step2(n) {console.log(`step2 with ${n}`)return getData(n)
}function step3(n) {console.log(`step3 with ${n}`)return getData(n)
}

4.1 使用 Promise 的方法来实现这三个处理

function getData(n) {return new Promise(resolve => {setTimeout(() => resolve(n + 200), n)})
}function step1(n) {console.log(`step1 with ${n}`)return getData(n)
}function step2(n) {console.log(`step2 with ${n}`)return getData(n)
}function step3(n) {console.log(`step3 with ${n}`)return getData(n)
}function getDataByPromise() {console.time('用时')const time1 = 300step1(time1).then((time2) => step2(time2)).then(time3 => step3(time3)).then(result => {console.log('最终结果是:', result)console.timeEnd('用时')})
}getDataByPromise()

输入结果如下图所示:

输出结果是 result 是 step3 的参数 900。getDataByPromise() 顺序执行了三个步骤,一共用时 300 + 500 + 700 = 1500 毫秒,和 console.time / console.timeEnd 的计算结果基本一致。

console.time() 和 console.timeEnd() 这两个方法可以用来让 Web开发工程师测量一个JavaScript 脚本程序执行消耗的时间,随着WEB应用越来越重要,JavaScript的执行性能也越发受到重视,Web开发人员知道一些性能测试机器是必须的。

4.2 使用 asycn / await 方式

如果换成 async / await来实现,写法应该是怎么样的,且执行时间有变化吗,且看下面代码。


function getData(n) {return new Promise(resolve => {setTimeout(() => resolve(n + 200), n)})
}function step1(n) {console.log(`step1 with ${n}`)return getData(n)
}function step2(n) {console.log(`step2 with ${n}`)return getData(n)
}function step3(n) {console.log(`step3 with ${n}`)return getData(n)
}async function getDataByAwait() {console.time('用时')const time1 = 300const time2 = await step1(time1)const time3 = await step2(time2)const result = await step3(time3)console.log('最终结果是:', result)console.timeEnd('用时')
}getDataByAwait()

输出结果如下图所示:


结果和之前的 Promise 结果是一样的,但相比之下,使用 async / await 方式的代码清晰明了,就像同步代码一样。

5. 更加凸显async / await 优势的例子,看完绝对会爱上它

将上面的代码实例的要求修改一下,仍然是3个步骤,但每一个步骤都需要用到之前的结果。

function getData(n) {return new Promise(resolve => {setTimeout(() => resolve(n + 200), n)})
}function step1(n) {console.log(`step1 with ${n}`)return getData(n)
}function step2(m, n) {console.log(`step2 with ${m} and ${n}`)return getData(m + n)
}function step3(k, m, n) {console.log(`step3 with ${k} and ${m} and ${n}`)return getData(k, m, n)
}

5.1 使用 async/await 方式

function getData(n) {return new Promise(resolve => {setTimeout(() => resolve(n + 200), n)})
}function step1(n) {console.log(`step1 值为 ${n}`)return getData(n)
}function step2(m, n) {console.log(`step2 值为 ${m} + ${n}`)return getData(m + n)
}function step3(k, m, n) {console.log(`step3 值为 ${k} + ${m} + ${n}`)return getData(k + m + n)
}async function getDataByAwait() {const time1 = 300console.time('用时')const time2 = await step1(time1)const time3 = await step2(time1, time2)const result = await step3(time1, time2, time3)console.log('最终结果是:', result)console.timeEnd('用时')
}getDataByAwait()

输出结果如下图所示:

看到这里,发现 使用 async / await 的写法和刚才好像没什么区别,只是执行时间变长了。

别急,等看完下面使用 Promise 的写法,你就会爱上 async / await。

5.2 使用 Promise 方式

function getDataByPromise() {const time1 = 300console.time('用时')step1(time1).then(time2 => {return step2(time1, time2).then(time3 => [time1, time2, time3])}).then(times => {const [time1, time2, time3] = timesreturn step3(time1, time2, time3)}).then(result => {console.log('最终结果是:', result)console.timeEnd('用时')})
}getDataByPromise()

输出结果为:


看到这里,是不是认为 async / await 的写法真的是太妙了,对于上面的逻辑来说,使用 Promise 的方式,写法太复杂了,尤其是那一堆参数传递处理,看着就令人头疼。

6. 使用 async/await 时,Promise 有可能返回 rejected 的状态

看到这里,相信大家对 async/await 有了一定的理解并且爱上了它。但我们还需要考虑一种情况:我们知道,Promise 被 new 了之后会有两种状态:fulfilled(成功)和 rejected(失败),上面的代码都是基于成功的状态,但Promise也有可能会返回 rejected 状态啊,如果不进行处理,页面会报错。下面会介绍 Promise 返回 rejected 状态时的处理方式。

我们在使用 async/await 的时候,由于 Promise 运行结果可能是 rejected,所以我们最好把 await 命令放在 try catch 代码块中。

举一个例子方便大家理解:

async getData = () => {try {await step1(200)} catch(err) {console.log(err)}
}// 另一种写法
async getData = () => {await step1(200).catch(err = > console.log(err))
}

【异步系列四】async await 原理解析之爱上 async/await相关推荐

  1. Git使用 从入门到入土 收藏吃灰系列(四) Git工作原理

    文章目录 一.前言 一.Git基本理论(核心) 1.1工作区 1.2工作流程 一.前言 参考安装Git 详细安装教程 参考视频B站 Git最新教程通俗易懂,这个有点长,感觉讲的精华不多 参考视频『Gi ...

  2. 【异步系列二】Promise原理及执行顺序详解

    前言 Promise 是 javascript 中非常重要的一环,熟悉它是必须的,而且在面试中也常常会问到相关面试题. 在了解 Promise 之前,需要了解什么是异步编程,可以参考我的一篇文章:Ja ...

  3. android黑科技系列——微信抢红包插件原理解析和开发实现

    一.前言 自从几年前微信添加抢红包的功能,微信的电商之旅算是正式开始正式火爆起来.但是作为Android开发者来说,我们在抢红包的同时意识到了很多问题,就是手动去抢红包的速度慢了,当然这些有很多原因导 ...

  4. 平衡小车制作系列之二——模块原理解析

    文章目录 一. 模块概述 二. 直流电机 2.1 直流电机介绍 2.2 直流电机外围设备介绍 2.2.1 减速器 2.2.2 控制PWM的单片机 2.2.3 编码器 三. 编码器 3.1 编码器介绍 ...

  5. Golang-Context扫盲与原理解析

    Golang-Context扫盲与原理解析 一.什么是Context? context是一个包,是Go1.7引入的标注库,中文译做上下文,准确的说是goroutine的上下文,包含goroutine的 ...

  6. iommu 工作原理解析之dma remapping

    深入了解iommu系列二:iommu 工作原理解析之dma remapping: https://zhuanlan.zhihu.com/p/479963917

  7. async js 返回值_JS异步编程 | Async / Await / Generator 实现原理解析

    async/await实现 在多个回调依赖的场景中,尽管Promise通过链式调用取代了回调嵌套,但过多的链式调用可读性仍然不佳,流程控制也不方便,ES7 提出的async 函数,终于让 JS 对于异 ...

  8. python await原理_JavaScript async/await原理及实例解析

    随着Node 7的发布,越来越多的人开始研究据说是异步编程终级解决方案的 async/await. 异步编程的最高境界,就是根本不用关心它是不是异步. async 函数就是隧道尽头的亮光,很多人认为它 ...

  9. “约见”面试官系列之常见面试题第四十四篇之webpack打包原理解析?(建议收藏)

    webpack打包是如何运行的 也可以称为,webpack是如何实现模块化的 CommonJS是同步加载模块,一般用于node.因为node应用程序运行在服务器上,程序通过文件系统可以直接读取到各个模 ...

最新文章

  1. Ural 1025 Democracy in Danger 解题报告
  2. Unity3D安卓程序中常用静态方法封装
  3. eja变送器故障代码al01_EJA系列差压变送器的使用及故障分析
  4. kbengine0.2.3发布,开源分布式游戏服务端引擎
  5. 值引用和引用问题分析
  6. html实现 页面禁止右键 禁止复制 禁止图片拖动 禁止复制和剪切
  7. php返回上一层的函数6,[PHP]实用函数6第1/2页
  8. Microsoft.CSharp.targets不存在解决方法
  9. Kubernetes负载均衡器-traefik ingress安装
  10. js截屏代码_服务端浏览器截屏
  11. centos7黑客帝国装逼
  12. 截取url的host_js如何准确获取当前页面url网址信息
  13. C语言求素数个数及素数之和
  14. Openwrt下ipk包的安装、卸载与更新
  15. 计算机的音频管理器在哪里打开,Realtek高清晰音频管理器怎么找不到打开教程...
  16. java发布geoserver样式(sld样式)
  17. linux 打印进程日志,Linux系统日志管理:(2)进程统计日志
  18. View补间动画Animation运行原理
  19. 服务器系统获取最高权限,webshell+serv-u获取系统最高权限
  20. laydate点击输入框闪一下不见了_ps文字工具打字不显示,应该如何解决?

热门文章

  1. 如何使用transformers的trainer.train()函数如何训练自定义Bert的下游模型,并进行评估
  2. 洛谷OJ P1263 宫廷守卫
  3. 怜惜,才是最真挚的爱情
  4. 医院信息中心如何提高满意度
  5. python超详细零基础 bs4解析之爬取唯美图片
  6. centos7上用nginx安装nextcloud(PHP72)
  7. angualr2 下载文件的两种方式
  8. AAPT2命令行使用总结
  9. 帮助 DeFi 顺利运行的秘密鲸鱼「清算人」是如何工作的?
  10. GDOI【JZOJ4793】妮厨的愤怒