19 async await使用同步方式写异步代码

Posted by CodingWithAlice on April 14, 2021

19 async await使用同步方式写异步代码

Promise 的缺点:处理流程复杂时,代码充斥了.then方法,语义化不明显

async/await :底层转换成了 Promisethen 回调函数 —> 每次我们使用 await, 解释器都创建一个 promise 对象,然后把剩下的 async 函数中的操作放到 then 回调函数中

JS 异步编程的重大改进 - async await

async await - 提供了在不阻塞主线程的情况下 使用同步代码实现异步访问资源 的能力,并且使得代码逻辑更加清晰

async/await - 基于promise的,更加优雅的写出代码,而替代 then() 的写法;

需要注意的就是 await是强制把异步变成了同步,这一句代码执行完,才会执行下一句

1572012187509

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 使用了 GeneratorPromise 两种技术

什么是 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 协程和父协程是在主线程上 交互执行 的,并不是并发执行 的,它们之前的切换是通过 yieldgen.next 来配合完成的。

  • 第二点

    当在 gen 协程中调用了 yield 方法时,JavaScript 引擎会保存 gen 协程当前的 调用栈信息,并恢复父协程的调用栈信息。同样,当在父协程中执行 gen.next 时,JavaScript 引擎会保存父协程的调用栈信息,并恢复 gen 协程的调用栈信息。

image-20210414134853816

生成器示例代码的执行过程

简单了解了协程的概念后,我们用协程看下生成器示例代码的执行过程

image-20210414135211223

生成器实现一个复杂业务

基于协程能够提供更好的性能,以及暂停/恢复特性,实现一个比较复杂的业务,可以如下考虑:

image-20210414135231872

async await语法糖的实现

async await 让开发彻底告别执行器和生成器,实现更加直观简洁的代码。

其实 async/await 技术背后的秘密就是 Promise 和生成器 应用,往底层说就是 微任务和协程 应用。

async

MDN描述:async 是一个通过异步执行隐式返回 Promise 作为结果的函数

async function foo() {return 2}
console.log(foo())  // Promise {<resolved>: 2}

await

结合 async 一起看下

image-20210414135259911

await 100;这个语句做了几件事:

1、默认 创建 一个 Promise 对象,其中的 resolve 函数被提交给 微任务队列

2、暂停 当前 foo 协程,并将创建的 promise_ 返回给父协程(async 后续代码作为 then 的回调)

3、父协程通过调用 promise_.then 方法监控 Promise 对象状态的改变

然后继续进行父协程,在父协程任务执行完,结束之前,到达检查点,检查微任务队列

–> 执行微任务列表中的 resolve(100) ,激活 .then 方法监控的回调函数,并将主线程的控制权交给 foo 协程,并将 100 传给协程

—> foo 协程被激活 后,将 value 值赋值给 a,继续执行后面的 console.log 语句

—> 执行完成后将主线程的控制权 归还给父协程