生成器/执行器/async/await

278 阅读3分钟

generator

带 * 的function可以中断函数执行

function* genDemo() { console.log(" 开始执行第一段 ") yield 'generator 2' console.log(" 开始执行第二段 ") yield 'generator 2' console.log(" 开始执行第三段 ") yield 'generator 2' console.log(" 执行结束 ") return 'generator 2' }

image.png

协程是一种比线程更加轻量级的存在。你可以把协程看成是跑在线程上的任务,一个线程上可以存在多个协程,但是在线程上同时只能执行一个协程,比如当前执行的是 A 协程,要启动 B 协程,那么 A 协程就需要将主线程的控制权交给 B 协程,这就体现在 A 协程暂停执行,B 协程恢复执行;同样,也可以从 B 协程中启动 A 协程。通常,如果从 A 协程启动 B 协程,我们就把 A 协程称为 B 协程的父协程

第一点:gen 协程和父协程是在主线程上交互执行的,并不是并发执行的,它们之前的切换是通过 yield 和 gen.next 来配合完成的。

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

image.png

async/await

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


async function foo() {
console.log(1)
let a = await 100
console.log(a)
console.log(2)
}
console.log(0)
foo()
console.log(3)

0 1 3 100 2

image.png

首先,执行console.log(0)这个语句,打印出来 0。

紧接着就是执行 foo 函数,由于 foo 函数是被 async 标记过的,所以当进入该函数的时候,JavaScript 引擎会保存当前的调用栈等信息,然后执行 foo 函数中的console.log(1)语句,并打印出 1。

接下来就执行到 foo 函数中的await 100这个语句了,这里是我们分析的重点,因为在执行await 100这个语句时,JavaScript 引擎在背后为我们默默做了太多的事情,那么下面我们就把这个语句拆开,来看看 JavaScript 到底都做了哪些事情。

当执行到await 100时,会默认创建一个 Promise 对象,代码如下所示:

let promise_ = new Promise((resolve,reject){
resolve(100)
})

在这个 promise_ 对象创建的过程中,我们可以看到在 executor 函数中调用了 resolve 函数,JavaScript 引擎会将该任务提交给微任务队列

然后 JavaScript 引擎会暂停当前协程的执行,将主线程的控制权转交给父协程执行,同时 会将 promise_ 对象返回给父协程。 主线程的控制权已经交给父协程了,这时候父协程要做的一件事是调用 promise_.then 来 监控 promise 状态的改变。 接下来继续执行父协程的流程,这里我们执行console.log(3),并打印出来 3。随后父 协程将执行结束,在结束之前,会进入微任务的检查点,然后执行微任务队列,微任务队列 中有resolve(100)的任务等待执行,执行到这里的时候,会触发 promise_.then 中的回 调函数,如下所示:

promise_.then((value)=>{
// 回调函数被激活后
// 将主线程控制权交给 foo 协程,并将 vaule 值传给协程
})

该回调函数被激活以后,会将主线程的控制权交给 foo 函数的协程,并同时将 value 值传 给该协程。 foo 协程激活之后,会把刚才的 value 值赋给了变量 a,然后 foo 协程继续执行后续语 句,执行完成之后,将控制权归还给父协程。 以上就是 await/async 的执行流程。正是因为 async 和 await 在背后为我们做了大量的工 作,所以我们才能用同步的方式写出异步代码来。