Generator和协程
生成器函数的具体使用方式:
在生成器函数内部执行一段代码,如果遇到 yield 关键字,那么 JavaScript 引擎将返回关键字后面的内容给外部,并暂停该函数的执行。
外部函数可以通过 next 方法恢复函数的执行
Generator返回的是一个协程,协程是一种比线程更轻量级的存在,你可以把协程看出是跑在线程上的任务,一个线程上可以存在多个协程,但是在线程上同时只能执行一个协程,比如当前执行的是A协程,要启动B协程,那么协程就需要把主线程的控制权交给B协程。如果从A协程启动B协程,把A协程称为B协程的父协程
一个进程拥有多个线程,一个线程也可以拥有多个协程,协程不是由操作系统内核管理,而完全是由程序控制(也就是用户态执行),好处就是性能得到了提升,不会像线程切换那样消耗资源
1 | function* genDemo() { |
协程四点规则:
- 调用生成器函数genDemo创建一个写成gen,创建后,gen协程并没有立即执行
- 要让gen协程执行,需要通过调用gen.next
- 当协程正在执行时,可以通过yield关键字来暂停gen协程的执行,并返回主信息给父协程
- 如果协程在执行期间,遇到return关键字,那么js引擎会结束当前协程,并将return后面的内容返回给父协程
父协程有自己的调用栈,gen 协程时也有自己的调用栈,当 gen 协程通过 yield 把控制权交给父协程时,V8 是如何切换到父协程的调用栈?当父协程通过 gen.next 恢复 gen 协程时,又是如何切换 gen 协程的调用栈?
要搞清楚上面的问题,你需要关注以下两点内容。
第一点:gen 协程和父协程是在主线程上交互执行的,并不是并发执行的,它们之前的切换是通过 yield 和 gen.next 来配合完成的。
第二点:当在 gen 协程中调用了 yield 方法时,JavaScript 引擎会保存 gen 协程当前的调用栈信息,并恢复父协程的调用栈信息。同样,当在父协程中执行 gen.next 时,JavaScript 引擎会保存父协程的调用栈信息,并恢复 gen 协程的调用栈信息。
使用Promise和generator:
1 | //foo 函数 |
在foo函数里实现了用同步方式实现异步操作,foo函数外部代码:
- let gen=foo()创建gen协程
- 父协程中通过执行gen.next把主线程控制权交给gen协程
- gen协程获取到主线程控制权,就调用fetch函数创建一个Promise对象reponse1,然后通过yield暂停gen协程的执行,将response1返回给父协程
- 父协程恢复执行后,调用reponse1.then方法等待结果
- 等通过fetch发起的请求完成后,会调用then中回调函数,then中的回调函数拿到结果后,通过调用gen.next放弃主线程控制权
把执行生成器的代码封装成一个函数,并把这个执行生成器代码的函数称为执行器
1 | function* foo() { |
async/await
async是一个通过异步执行并隐式返回Promise作为结果的函数
调用async的foo函数返回一个Promise对象,
1 | async function foo() { |
foo函数被async标记,当进入该函数时,js引擎会保存当前调用栈信息,当执行到await(100),会默认创建一个Promise对象。
1 | let promise_ = new Promise((resolve,reject){ |
在这个promise__对象创建过程中,executor函数调用resolve函数,js引擎会将该任务提交给微任务,然后js引擎会暂停当前协程执行,将主线程的控制权交给父协程执行,同时将promise__对象返回给父协程,主线程的控制权已经交给父协程,这时候父协程要做的事就是调用promise_.then监控 promise状态的改变。继续执行父协程的流程,执行console.log(3),父协程执行结束后,在结束之前,会进入微任务检查点,执行微任务队列,微任务队列有resolve(100),触发promise_.then的回调函数
1 | promise_.then((value)=>{ |