V8是如何实现async/await的?

一、异步编程方案史

image-20201228190227226

二、Generator函数

Generator函数:function*,配合yield可以实现函数的暂停和恢复执行。

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
/**
* 分析执行过程
*/

function getData (i) {
return Promise.resolve('getData' + i)
}

function* genFn () {
console.log(1)
yield getData(1)
console.log(2)
yield getData(2)
console.log(3)
return 'finish'
}

var gen = genFn()
console.log(gen.next())
console.log(gen.next())
console.log(gen.next())

/*
1
{value: Promise{<pending>},done: false}
2
{value: Promise{<pending>},done: false}
3
{value: 'finish',done: true}
getData1
getData2
*/

在Generator函数内部,如果遇到yield,那么,V8将返回yield后面的内容给外部,并暂停Generator函数的执行。Generator函数暂停执行后,外部的代码便开始执行,外部代码如果想要恢复Generator函数的执行,可以使用 next 方法。

那么,V8是如何实现Generator函数的暂停和恢复执行的???

那就是协程

三、协程

协程:一种比线程更加轻量级的存在。

可以把协程看做是跑在线程上的任务,一个线程可以存在多个协程,但在线程上同时只能执行一个协程,同时,协程不是被操作系统内核管理的,而是由程序所控制的(也就是在用户态执行),这样协程切换不会太消耗资源。

通常,如果从A协程启动B协程,就把A协程成为B协程的父协程。

在JavaScript中,生成器就是协程的一种实现方式。

但是光有生成器还不够,每次都要手动去暂停和恢复,所以又引入了执行器,著名的co,就是一个执行器

四、终极方案async/await

由于需要引入 co 来充当执行器,这一点还不够友好。

所以ES7引入了async/await,它改进了生成器的缺点,提供了在不阻塞主线程的情况下使用同步的方式实现异步访问资源的能力。

其背后的原理就是Promise和生成器,再细一点的说就是微任务和协程。

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
/**
* 分析执行过程
*/

function getData (i) {
return Promise.resolve('getData' + i)
}

async function genFn () {
console.log(1)
let res1 = await getData(1)
console.log(res1)
console.log(2)
let res2 = await getData(2)
console.log(res2)
console.log(3)
return 'finish'
}

var gen = genFn()
console.log(gen)

/*
1
Promise {<pending>}
__proto__: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: "finish"
getData1
2
getData2
3
*/

async是一个通过异步执行并隐式返回Promise实例作为结果的函数。

await后面可以跟两种类型的表达式:

  • 任何普通表达式:会被包装成Promise对象
  • Promise对象的表达式

如果在async函数里使用了await,那么执行到await时,就会暂停执行,等待await后面的表达式有结果了再返回给外面,也就是等到Promise变成resolved,然后再恢复执行。

其实async声明的函数在执行时,就相当于创建了一个单独的协程,可以通过await来暂停执行,等到await后面的Promise表达式变成resolved,再恢复执行。

参考链接