• async await 原理解析之爱上 async/await


    前言

    异步编程一直是 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 函数返回的是什么?以下面代码为例

    ![在这里插入图片描述](https://img-blog.csdnimg.cn/e54afb6535674b1d9ae1bb52475a80b6.png

    看到这里,就会发现,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 {: 123}
    // 123
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    从这里我们可以看出来,带有 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 如果 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)
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    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 = 300
    	step1(time1).then((time2) => step2(time2))
    				.then(time3 => step3(time3))
    				.then(result => {
    					console.log('最终结果是:', result)
    					console.timeEnd('用时')
    				})
    }
    
    getDataByPromise()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    输入结果如下图所示:

    在这里插入图片描述

    输出结果是 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 = 300
    	const time2 = await step1(time1)
    	const time3 = await step2(time2)
    	const result = await step3(time3)
    	console.log('最终结果是:', result)
    	console.timeEnd('用时')
    }
    
    getDataByAwait()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    输出结果如下图所示:

    在这里插入图片描述
    结果和之前的 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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    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 = 300
    	console.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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    输出结果如下图所示:

    在这里插入图片描述

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

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

    5.2 使用 Promise 方式

    function getDataByPromise() {
    	const time1 = 300
    	console.time('用时')
    	step1(time1).then(time2 => {
    		return step2(time1, time2).then(time3 => [time1, time2, time3])
    	}).then(times => {
    		const [time1, time2, time3] = times
    		return step3(time1, time2, time3)
    	}).then(result => {
    		console.log('最终结果是:', result)
    		console.timeEnd('用时')
    	})
    }
    
    getDataByPromise()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    输出结果为:

    在这里插入图片描述
    看到这里,是不是认为 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))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
  • 相关阅读:
    第七章 Java编程-多线程
    c++ 智能指针 shared_ptr
    CSS3层叠样式表
    【跟小嘉学 Rust 编程】三十三、Rust的Web开发框架之一: Actix-Web的基础
    微擎模块 微信小程序kǎn jià宝7.2.0开源版
    闪存工作原理
    Python OpenCV将n×n的小图拼接成m×m的大图
    第一届长城杯半决赛wp和AWD笔记
    [原创]JVM知识点盘点
    瑞芯微 | I2S-音频基础分享
  • 原文地址:https://blog.csdn.net/qq_41131745/article/details/127076378