温故而知新,当我再次遇到 Async、Await 时对其中的细节有些许遗忘。所以写下这篇之前就该写下的文章。
利用 Babel,将 Async、Await 进行转换,可以发现 Async、Await 利用了 switchcase、promise 来达到流程控制。如下是一段使用了 Async、Await 的函数:
async
为了方便理解,下面只给出核心部分代码。
前面的 name 函数经过 Babel 转换后(后面不说明,name 函数均指被 babel 转换后的 name 函数),得到如下的代码:
...
函数体分段
可以看到经过转换后的函数将 async 函数体内部分成了几个部分,分别为 await 部分、return 部分、async 流程控制结束部分(即 case "end")。如下图所示:
Async 函数就是将函数体内部进行拆分,这样就可以进行流程控制,例如 case 0 执行后,可以等到合适的时机去执行 case 1。
那么这个合适的时机是什么呢?其实这个时机并不是 async 内部决定的,而是由执行的内容决定,例如发一个异步请求,需要等到请求返回之后才会进到下一个 case。
下面介绍 Async 如何利用 promise 来实现等待,并且等待完成后如何进到一下个 case。
流程控制
要实现流程控制还需要 regenerator-runtime 这个 generator、Async 函数的运行时,它负责将 name 函数进行包装,并添加一些流程控制所需的必要信息。例如 _context:
{
另外还需要 _asyncToGenerator、asyncGeneratorStep 这两个 Babel 的 helper,在流程控制中也起到了关键的作用。
用它们在 regenerator-runtime 的基础之上,再包装一层得到一个最终包装好的函数,所以 name 函数实际执行时,调用的最终是这个函数。它的大致内容如下:
...
这里的第三步是 Async 函数的精华,Promise.resolve(value).then(_next) 中的 value 是每个分段最后的 case 所返回的值,如果是一个 Promise 则等到它 resolved 后将 .then 添加到微任务队列,否则直接添加,因为 .then 是一个微任务,当执行到它时会执行 _next ,便开始执行下一个 case。
async
上面的代码经过转换后如下
function
别忘了前面说过的最终被包装后的函数,实际执行的是被包装过的函数,所以我在这里说的 async1、async2 执行实际上是执行最终的被包装过的函数,被包装后的函数会在内部调用 async1、async2。
- 第一步调用 async1(),执行_next 函数,进入 async1 的 case 0 并打印 'async1 start',设置 _context.next 为 3,即下一步要走的 case 为 3
- 执行 return async2(),执行_next 函数,进入 async2 的 case 0 并打印 'async2',由于没有 return,直接进到 case "end"。这个时候会把 done 设置为 true,接下再次进入到 _next,根据 done 的值可以得到能直接结束整个流程,于是执行 resolve(value),async2 函数执行完毕。接下来回到 async1 中的 return async2(),async2 返回的是一个 Promise,所以这里 return 了一个已经 resolved 的 Promise。
- 此时 async1 的 case 0 执行结束,return 的 value 是一个 Promise,接下来再次进入 _next,由于还未走到 case "end",所以 done 为 false,所以执行 Promise.resolve(value).then(_next),由于 value 是已经 resolved 的 Promise,所以直接将 .then 添加到微任务队列中。由于还有同步任务未执行完,所以微任务队列还不会被执行,所以此时将权限交给 async1 函数的外部去执行。
- 执行 new Promise 并打印 'promise1' ,并将 .then 添加到微任务队列中去
- 同步代码执行完毕,开始执行微任务,首先执行之前 async1 添加进去的 _next,此时走到 async1 的 case 3 并打印 'async1 end',然后紧接着到 case 'end',最后当再次走到 _next 的时候发现 async1 可以结束,于是直接 resolve 结束执行
- 执行第二个微任务,打印 'promise'