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

相关推荐
也无晴也无风雨1 小时前
深入剖析输入URL按下回车,浏览器做了什么
前端·后端·计算机网络
Martin -Tang2 小时前
Vue 3 中,ref 和 reactive的区别
前端·javascript·vue.js
FakeOccupational3 小时前
nodejs 020: React语法规则 props和state
前端·javascript·react.js
放逐者-保持本心,方可放逐4 小时前
react 组件应用
开发语言·前端·javascript·react.js·前端框架
曹天骄5 小时前
next中服务端组件共享接口数据
前端·javascript·react.js
阮少年、5 小时前
java后台生成模拟聊天截图并返回给前端
java·开发语言·前端
郝晨妤6 小时前
鸿蒙ArkTS和TS有什么区别?
前端·javascript·typescript·鸿蒙
AvatarGiser7 小时前
《ElementPlus 与 ElementUI 差异集合》Icon 图标 More 差异说明
前端·vue.js·elementui
喝旺仔la7 小时前
vue的样式知识点
前端·javascript·vue.js
别忘了微笑_cuicui7 小时前
elementUI中2个日期组件实现开始时间、结束时间(禁用日期面板、控制开始时间不能超过结束时间的时分秒)实现方案
前端·javascript·elementui