Async、Await 从源码层面解析其工作原理

2022-12-19 0 540

温故而知新,当我再度碰到 Async、Await 时对当中的技术细节有少许忘却。因此写出这篇以后就该写出的该文。

借助 Babel,将 Async、Await 展开切换,能辨认出 Async、Await 利用了 switchcase、promise 来达至业务流程掌控。如下表所示是几段采用了 Async、Await 的表达式:

async function name () { console.log(async name) const a = await name1() console.log(async name2) return 1 } async function name1(){ console.log(async name1) }

为的是方便快捷理解,上面只得出核心理念部份标识符。

前面的 name 表达式历经 Babel 切换后(前面不表明,name 表达式均指被 babel 切换后的 name 表达式),获得如下表所示的标识符:

var a while (1) { switch (_context.prev = _context.next) { case 0: console.log(async name); _context.next = 3; return name1(); case 3: console.log(async name2); a = _context.sent; return _context.abrupt(“return”, 1); case 5: case “end”: return _context.stop(); } }

表达式体分段

能看到历经切换后的表达式将 async 表达式体内部份成了几个部份,分别为 await 部份、return 部份、async 业务流程掌控结束部份(即 case “end”)。如下表所示图所示:

Async、Await 从源码层面解析其工作原理

Async 表达式就是将表达式体内部展开拆分,这样就能展开业务流程掌控,例如 case 0 执行后,能等到合适的时机去执行 case 1。

那么这个合适的时机是什么呢?其实这个时机并不是 async 内部决定的,而是由执行的内容决定,例如发一个异步请求,需要等到请求返回之后才会进到下一个 case。

上面介绍 Async 如何借助 promise 来实现等待,并且等待完成后如何进到一下个 case。

业务流程掌控

要实现业务流程掌控还需要 regenerator-runtime 这个 generator、Async 表达式的运行时,它负责将 name 表达式展开包装,并添加一些业务流程掌控所需的必要信息。例如 _context:

{ pre: 0, // 当前走哪个 case next: 3, // 下一个走哪个 case value: Promise , // 每个分段最后的 case 返回的值,前面会说到 await xxx 时,xxx 一般是一个 async 表达式,会返回一个 Promise done: false // 当前是否以及走到了 case “end”,如果是需要结束业务流程 }

另外还需要 _asyncToGenerator、asyncGeneratorStep 这两个 Babel 的 helper,在业务流程掌控中也起到了关键的作用。

用它们在 regenerator-runtime 的基础之上,再包装一层获得一个最终包装好的表达式,因此 name 表达式实际执行时,调用的最终是这个表达式。它的大致内容如下表所示:

return new Promise((resolve, reject) => { function _next(value) { 1调用 name 表达式根据 _context.next 决定当前进哪个 case 执行拿到返回值 value 2判断 done 是否为 true true 调用 resolve(value)结束执行否者展开第 3 3Promise.resolve(value).then(_next) 准备进入下一个 case 执行标识符 } _next(undefined) })

这里的第三步是 Async 表达式的精华,Promise.resolve(value).then(_next) 中的 value 是每个分段最后的 case 所返回的值,如果是一个 Promise 则等到它 resolved 后将 .then 添加到微任务队列,否则直接添加,因为 .then 是一个微任务,当执行到它时会执行 _next ,便开始执行下一个 case。

async function async1() { console.log(async1 start); await async2(); console.log(async1 end) } async function async2() { console.log(async2); } async1(); new Promise(function (resolve) { console.log(promise1); resolve(); }).then(function () { console.log(promise2); });

上面的标识符历经切换后如下表所示

function async1() { while (1) { switch (_context.prev = _context.next) { case 0: console.log(async1 start); _context.next = 3; return async2(); case 3: console.log(async1 end); // 如果最后没有 return 的话,将其与 case “end” 合并 case 4: case “end”: return _context.stop(); } } } function async2() { while (1) { switch (_context2.prev = _context2.next) { case 0: console.log(async2); // 如果最后没有 return 的话,将其与 case “end” 合并 case 1: case “end”: return _context2.stop(); } } } async1(); new Promise(function (resolve) { console.log(promise1); resolve(); }).then(function () { console.log(promise2); });

别忘了前面说过的最终被包装后的表达式,实际执行的是被包装过的表达式,因此我在这里说的 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

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务