19 async await使用同步方式写异步代码
Promise 的缺点:处理流程复杂时,代码充斥了.then
方法,语义化不明显
async/await
:底层转换成了 Promise
和 then
回调函数 —> 每次我们使用 await
, 解释器都创建一个 promise 对象,然后把剩下的 async
函数中的操作放到 then 回调函数中。
JS 异步编程的重大改进 - async await
async await - 提供了在不阻塞主线程的情况下 使用同步代码实现异步访问资源 的能力,并且使得代码逻辑更加清晰
async/await - 基于promise的,更加优雅的写出代码,而替代 then()
的写法;
需要注意的就是 await是强制把异步变成了同步,这一句代码执行完,才会执行下一句。
在 async 函数内部可以使用 await 命令,表示等待一个异步函数的返回。await 后面跟着的是一个 Promise 对象,如果不是的话,系统会调用 Promise.resolve()
方法,将其转为一个 resolve 的 Promise 的对象。
let bar = async function(){
try{
await Promise.reject('error')
}catch(e){
console.log(e)
}
}
JS 引擎如何实现 async/await
async/await
使用了 Generator
和 Promise
两种技术
什么是 Generator 生成器
生成器函数是一个带星号函数,而且是 可以暂停执行和恢复执行 的。
具体使用方式:
- 在生成器函数内部执行一段代码,如果遇到 yield 关键字,那么 JS 引擎将返回关键字后面的内容给外部,并暂停该函数的执行
- 外部函数可以通过 next 方法恢复函数的执行
示例代码:
// 已备注执行顺序
function* genDemo() {
console.log(" 开始执行第一段 "); // --> 2
yield 'generator one'
console.log(" 开始执行第二段 ") // --> 5
yield 'generator two'
console.log(" 开始执行第三段 ") // --> 8
yield 'generator three'
console.log(" 执行结束 ") // --> 11
return 'generator four'
}
console.log('main 0') // --> 1
let gen = genDemo()
console.log(gen.next().value) // --> 3: generator one
console.log('main 1') // --> 4
console.log(gen.next().value) // --> 6: generator two
console.log('main 2') // --> 7
console.log(gen.next().value) // --> 9: generator three
console.log('main 3') // --> 10
console.log(gen.next().value) // --> 12: generator four
console.log('main 4') // --> 13
生成器(Generator)是如何工作的?
JS引擎是如何实现一个函数的暂停与恢复执行的? – 首先要了解协程的概念
Generator 的底层实现机制——协程
-
协程定义
协程是一种比线程更加轻量级的存在。你可以把协程看成是 跑在线程上的任务,一个线程上可以存在多个协程,但是在线程上 同时只能执行一个协程
-
协程优势
协程 不是被操作系统内核所管理,而完全是 由程序所控制(也就是在用户态执行)。这样带来的好处就是性能得到了很大的提升,不会像线程切换那样消耗资源
-
协程的特征
1、调用生成器创建的协程,创建之后并不会直接执行
2、想要协程执行,需要调用
.next
方法3、协程正在执行的时候,可以通过
yield
暂停协程,并返回信息给父协程4、在协程的执行期间,遇到了
return
关键词,JS引擎会结束当前协程,并返回给父协程
切换协程的调用栈实现
-
第一点:主线程上,gen 协程和父协程交互执行,而非并发
gen 协程和父协程是在主线程上 交互执行 的,并不是并发执行 的,它们之前的切换是通过
yield
和gen.next
来配合完成的。 -
第二点
当在 gen 协程中调用了
yield
方法时,JavaScript
引擎会保存 gen 协程当前的 调用栈信息,并恢复父协程的调用栈信息。同样,当在父协程中执行gen.next
时,JavaScript 引擎会保存父协程的调用栈信息,并恢复 gen 协程的调用栈信息。
生成器示例代码的执行过程
简单了解了协程的概念后,我们用协程看下生成器示例代码的执行过程
生成器实现一个复杂业务
基于协程能够提供更好的性能,以及暂停/恢复特性,实现一个比较复杂的业务,可以如下考虑:
async await语法糖的实现
async await
让开发彻底告别执行器和生成器,实现更加直观简洁的代码。
其实 async/await
技术背后的秘密就是 Promise 和生成器 应用,往底层说就是 微任务和协程 应用。
async
MDN描述:async
是一个通过异步执行并隐式返回 Promise 作为结果的函数
async function foo() {return 2}
console.log(foo()) // Promise {<resolved>: 2}
await
结合 async
一起看下
await 100;
这个语句做了几件事:
1、默认 创建 一个 Promise
对象,其中的 resolve
函数被提交给 微任务队列
2、暂停 当前 foo
协程,并将创建的 promise_
返回给父协程(async 后续代码作为 then 的回调)
3、父协程通过调用 promise_.then
方法监控 Promise
对象状态的改变
然后继续进行父协程,在父协程任务执行完,结束之前,到达检查点,检查微任务队列
–> 执行微任务列表中的 resolve(100)
,激活 .then
方法监控的回调函数,并将主线程的控制权交给 foo
协程,并将 100
传给协程
—> foo
协程被激活 后,将 value
值赋值给 a
,继续执行后面的 console.log
语句
—> 执行完成后将主线程的控制权 归还给父协程