Skip to content

async 和 await 是怎么工作的?——生成器与协程 #182

Open
@TieMuZhen

Description

@TieMuZhen

什么是生成器函数?

生成器函数是一个带*的函数,可以暂停执行与恢复执行。 async/await 使用了 协程(Generator) 和 微任务(Promise) 两种技术来实现。

function* genDemo() {
  console.log("开始执行第 1 段");
  yield "generator 1";

  console.log("开始执行第 2 段");
  yield "generator 2";

  console.log("执行结束");
  return "generator 3";
}

console.log("main 0");
let gen = genDemo(); // 此处不会打印"开始执行第 1 段"只有在执行 gen.next 时才会执行
console.log(gen.next().value); // 开始执行第 1 段  generator 1
console.log("main 1");
console.log(gen.next().value);
console.log("main 2");
console.log(gen.next().value);
console.log("main 3");

输出内容如下

main 0
开始执行第 1 段
generator 1
main 1
开始执行第 2 段
generator 2
main 2
执行结束
generator 3
main 3

从上面输出结果可以看出,生成器函数与主函数是交替执行的。
生成器函数中遇到yield关键字时,就会返回yield后的内容给外部并把执行权交给外部函数去执行。
外部函数又可以通过gen.next恢复生成器函数的执行。

什么是协程?

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

从图中可以看出:
1、通过生成器函数genDemo创建的协程gen创建之后并没有立即执行。
2、通过调用gen.next可以使协程执行。
3、通过yield关键字来暂停gen协程的执行,并返回主要信息给父协程。
4、若在执行期间遇到return,JS 引擎会结束当前协程并将return后的内容返回给父协程。

父协程与gen协程都有自己的调用栈,当控制权通过yieldgen.next互相切换时,V8 是如何切换调用栈的?

gen协程与父协程是在主线程上交互执行的,并不是并发执行的,它们之间的切换是通过yieldgen.next配合完成。

gen中调用yield时,JS 引擎会保存gen协程当前的调用栈信息并恢复父协程的调用栈信息。同理,父协程中执行gen.next时,JS 引擎会保存父协程调用栈信息并恢复gen协程的调用栈信息。如下图:
image

async/await

async 是什么?

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

  • 隐式返回 Promise
async function foo() {
  return 2;
}
foo(); // Promise {<resolved>: 2}

await 是什么?

观察下面代码的输出:

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

当执行到await 100时,会创建一个Promise对象,如下:

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

JS 引擎会将该任务提交到微任务队列,然后暂停当前协程的执行,将主线程的控制权转交给父协程执行,同时将promise_对象返回给父协程(如下)。

async function foo() {
  ...
  let a = await 100
  ...
}
console.log(foo())
//Promise {<pending>}__proto__: ... "
// [[PromiseStatus]]: "resolved"
// [[PromiseValue]]: undefined

主线程控制权交给父协程后,父协程调用promise_.then来监控promise状态的改变。
接下来执行父协程的流程,打印出3。随后父协程将执行结束,在结束前,进入微任务的检查点去执行微任务队列,微任务队列中有 resolve(100)等待执行,执行到这里时,会触发promise_.then中的回调函数,如下:

promise_.then(value => {
  // 回调函数触发后,将主线程的控制权交给 foo 协程,并将 value 传给协程
});

foo协程激活后,将value的值给了变量a,然后继续执行后面语句,执行完成,将控制权归还给父协程。

参考文献

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions