async/await 原理解析

前言

对于异步编程来说, async/await大大简化了异步操作,异步操作可以写成同步写法,如果不了解 async/await 可以点击🔗MDN async 函数了解更多关于 async 知识

编译async/await

在平时工作中,可能会遇到 async/await,比如有这样的例子

a 函数定义为async ,同时在内部使用 await,并把 await 的返回结果作为普通函数 b 的实参

来看这个 async 函数 a 编译后的代码

🔗查看async/await源码

解析

首先在 async 的函数 a 中,返回了 __awaiter 函数的返回值,并在函数内部传递了4个实参,分别是 this,undefined,undefined,function *

🔔 其中 void 0 可以看做不可变 undefined,因为 undefined 不是关键字,可以被修改,所以使用 void 0 来代替 undefined

__awaiter 是一个高阶函数,在内部返回了一个 Promise

new (P || P = new Promise)(function(){}) , 由于 P 是 undefined,所以 P 被赋值给 Promise

Promise 内部中,默认执行了 step 方法,同时把 第四个参数 (即一个生成器函数 执行后的 next 对象) 作为实参传递给 step,如果不理解生成器函数的返回结果,可以看下面的例子简单了解一下,更多细节请查看🔗MDN关于# function*

js 复制代码
function* gen() {
  yield 10;
  x = yield "foo";
  yield x;
}

var gen_obj = gen();

// { value: 10, done: false }
console.log(gen_obj.next()); // 执行 yield 10
// { value: 'foo', done: false }
console.log(gen_obj.next()); // 执行 yield 'foo'

// { value: 100, done: false }
console.log(gen_obj.next(100)); // 将 100 赋给上一条 yield 'foo' 的左值,即执行 x=100,返回 100
// { value: undefined, done: true }
console.log(gen_obj.next()); // 执行完毕,value 为 undefined,done 为 true

以下解释来自 🔗MDN
调用一个生成器函数 并不会马上执行它里面的语句,而是返回一个这个生成器的 迭代器 iterator )对象 。当这个迭代器的 next() 方法被首次(后续)调用时,其内的语句会执行到第一个(后续)出现yield的位置为止,yield 后紧跟迭代器要返回的值。或者如果用的是 yield*(多了个星号),则表示将执行权移交给另一个生成器函数(当前生成器暂停执行)。
next()方法返回一个对象,这个对象包含两个属性:value 和 done,value 属性表示本次 yield 表达式的返回值,done 属性为布尔类型,表示生成器后续是否还有 yield 语句,即生成器函数是否已经执行完毕并返回。
调用 next()方法时,如果传入了参数,那么这个参数会传给上一条执行的 yield 语句左边的变量

❤️简单来说,通过执行 function* 返回一个迭代器对象, 这个对象有 next 方法,每调用一次 next 方法,就会执行到遇到 yield为止,然后返回一个包含 value 字段和 done 字段 的对象, value 字段代表本次 yield 的返回值,done 表示是否完成,再次调用,遇到下个 yield 会再次停止

function * 其实就是让程序员控制函数的执行步骤

🔗关于生成器方法更多示例代码


回到 step 函数上来, 那么只要是 donetrue 的情况,说明 生成器函数 已经执行完毕,这时候 Promise 就可以结束了,如果没有结束,那么就需要不断的执行迭代器,直到 donetrue

剩下的就是不断执行器,直到 donetrue

执行 adopt 这个辅助方法,传入执行迭代器 返回的值,adopt 返回 Promise,然后再把 resolve 后的值传递给内部的 fulfilled 方法,在 fulfilled 中继续执行 step 方法

由于 P 是 void 0,所以 P 被赋值为 Promise, adopt 定义为 Promise 是为了解决异步问题

根据核心逻辑作了一些简化

js 复制代码
var __awaiter = function (thisArg, generator) {
  return new Promise(function (resolve) {

    function step(result) {
      // 为 true 说明执行完毕,直接结束
      result.done
        ? resolve(result.value)
        //  再次创建一个 Promise,因为 Promise 可以处理异步,然后递归执行 step 方法
        : Promise.resolve(result.value).then((res)=>step(generator.next(res)));
    }

    step((generator = generator.apply(thisArg, undefined)).next());
  });
};


//  async function a() {
//    let r = await 1;
//     await 2;
//     return b(r)
//   }
//  function b(r){
//   return r
//  }
//  
//   a().then(res=>{
//     console.log(res)
//   })
//

function a() {
  return __awaiter(this,function* () {
    let r = yield 1;
    yield 2;
    return b(r);
  });
}

function b(r) {
  return r;
}

a().then(res => {
  console.log(res);
});

最后看一道面试题来加强对 async /await 的认识

碰见 async 就当做普通函数对待,因为只有 await 才会被转化为 yield, 所以 asyncfn1 可以看做

js 复制代码
function asyncfn1(){
   return __awaiter(this,function* () {
    console.log(2)
    let r = yield asyncfn2();
    console.log(3)
  });
}

由于 __awaiter 方法中迭代器函数会被默认执行一次,即 step(generator = generator.apply(thisArg, undefined)).next()),因为 console(2)yield 前面,不受 yield 影响,所以会同步输出, 同样的 asyncfn2 也是同样的道理

所以最终结果是 1 2 4 5 3 6

相关推荐
腾讯TNTWeb前端团队5 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰9 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪9 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪9 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy10 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom10 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom10 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom11 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom11 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom11 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试