es6-async await 手写 前端进阶必备(包懂)

12,983 阅读5分钟

这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战

      虽然上次的表白计划不了了之了,但我依然每天对她嘘寒问暖,我总相信,只要我对她好,总有一天我会感动她的。我也开始了所谓的舔狗之路......
      一次和她的聊天中知道她喜欢吃大后街的一家早餐,但每天都起不来。然后我提出帮她带早餐,起初她还觉得有点不好意思稍稍有拒绝的意思,后来在我一系列劝说下答应了,她答应的那一刻,我高兴的像个孩子,那是我们除同学之外的第一个交集,也给我无限的遐想和期望。
      大后街还是有点远的,为了防止面条软了,我也特意去买了辆自行车,还记得第一次买早餐的时候,我给自己定了6点的闹钟,兴奋、期待、紧张的我4点多就醒了,然后就毫无睡意了,脑子里一直在想等会应该和她说些啥,在哪里等她下来.....
      给她带早餐的第六天,她说她来那个了,下来不了了,她的室友帮她下来拿的,那时候的我对这方面真的不懂,我们那的人感觉都太保守了,对这种东西比较隐晦,于是我百度了一下,然后叮嘱她多喝红糖水,多休息之类的话。聊着聊着我就怀着好奇问了一下她:“这大姨妈一个月来几次哇?”。这句话也让我成为了大学班上四年的笑话,至今还被他们提起.....

前言提示

各位掘友们,此篇文章需要从头认真往下阅读,不然无法理解下文我通过生成器*函数和yield实现的async awit。 认真阅读包你搞透async 生成器 迭代器这些知识点。

整理知识点不易,如果大家学到了,帮我点赞关注,后续我会继续写进阶知识点。

迭代器、生成器、async

迭代器:迭代器是一种特殊对象,它具有一些专门为迭代过程设计的专有接口,所有的迭代器对象都有一个next()方法,每次调用都返回一个结果对象。结果对象有两个属性:一个是value,表示下一个将要返回的值;另一个是done,它是一个布尔类型的值,当没有更多可返回数据时返回true。迭代器还会保存一个内部指针,用来指向当前集合中值的位置,每调用一次next()方法,都会返回下一个可用的值

如何区分是否能迭代
     let arr=[1,2,3]
     let str='123'
     let obj={a:1,b:2}
     
     for(var i of arr){
         console.log('数组',i)
     }

     for(var i of str){
         console.log('字符串',i)
     }

     for(var i of obj){
         console.log('对象',i)
     }

打印结果如下图

image.png

说明: 对象是不可迭代的

可以从原型去看一个类型是否支持迭代,以字符串为例

image.png 展开字符串的原型,会发现它存在Symbol(Symbol.iterator)这个属性,说明它是可以迭代的。

把这个属性用起来,再结合迭代器的描述,你或许能进一步理解迭代器。
let str1='舔狗的泪' 为例

image.png

手动实现一个迭代器效果 以对象为例
   var obj = {
      a: 1,
      b: 2,
      c: 3,
      [Symbol.iterator]() {
        var index = 0;
        let map = new Map([
          ["a", 1],
          ["b", 2],
          ["c", 3],
        ]);
        return {
          next() {
            let mapEntries = [...map.entries()];//将它还原成二维数组
            if (index < map.size) {
              return {
                value: mapEntries[index++],
                done: false,
              };
            } else {
              return { value: undefined, done: true };
            }
          },
        };
      },
    };
    let iter=obj[Symbol.iterator]()

测试下,同时会发现这个对象是能支持for of 的

image.png

image.png

遍历和迭代的区别

迭代:从目标源依次逐个抽取的方式来提取数据。目标源是有序,连续
遍历:只要能够循环数据,并不需要有序。
因为对象循环不是有序的,所有它无法迭代,但是Map是可以支持迭代的。

生成器

函数前带 * 表示生成器 用yield去产出它的值

  function * test(){
      yield 1
      yield 2
      yield 3
    }
  var iter=test()
  iter.next()
  //输出 {value:1,done:false}

iter也是可以通过for of循环,这里不演示了

注意:每次调用next,他都会走到一个yield,下面代码不会打印log

  function * test(){
      console.log('舔狗的泪')
      yield 1
      yield 2
      yield 3
    }
  var iter=test()
  //要调用iter.next()才会走console.log 

将生成器的yield 换成return 看看区别:

image.png

基于yield去改造刚才让对象能够迭代的方法
     var obj = {
        a: 1,
        b: 2,
        c: 3,
        [Symbol.iterator]:function * () {
          var index = 0;
          let map = new Map([
            ['a', 1],
            ['b', 2],
            ['c', 3]
          ]);
          let mapEntries = [...map.entries()]; //将它还原成二维数组
          while(index<mapEntries.length){
            yield mapEntries[index++]
          }
        }
      };
      let iter = obj[Symbol.iterator]();
对next方法传参,看看yield丑陋的一面,async前世
      function* test() {
        let value1 = yield 1;
        console.log('value1',value1);
        let value2 = yield 2;
        console.log('value2',value2);
        let value3 = yield 3;
        console.log('value3',value3);
      }
      var iter=test()
      iter.next('one')
      iter.next('two')
      iter.next('three')
      iter.next('')

image.png 结果分析:next里面的值会赋给上一次yield 的值 。
站在开发者角度更期望 value1 打印的值就是1。async await它来了

async await基本操作

async await常见用法: let x=await 异步请求
看看它的其他用法,直接看图说话:

     function b() {
        return new Promise(resolve => {
          setTimeout(() => resolve(1), 1000);
        });
      }
      async function a() {
        let value = await b();
        console.log(value);
      }
      a();
      //1秒后打印1
     function b(){
        return 1
      }
      async function a() {
        let value = await b();
        console.log(value);
      }
      a();
      //打印1
      function b() {
        let c = 1;
      }
      async function a() {
        let a = await b();
        console.log(a);
      }
      a();
       //打印undefined
基于* yield实现一个async

以下面例子去实现:

      let num=1
      //b函数只为模拟一个axios
      function b() {
        return new Promise(resolve => {
          setTimeout(() => resolve(num++), 1000);
        });
      }
      async function a() {
        let value1 = await b();
        let value2 = await b();
        let value3 = await b();
        console.log(value1,value2,value3);
        return value3
      }
      let promise =a();
      //3秒后打印 1,2,3

基于生成器 递归实现,需要先看上面的对next方法传参,你才能明白下图

      let num = 1;
      function b() {
        return new Promise(resolve => {
          setTimeout(() => resolve(num++), 1000);
        });
      }
      function* a() {
        let value1 = yield b();
        let value2 = yield b();
        let value3 = yield b();
        console.log(value1, value2, value3);
        return value3
      }
      function Co(iter) {
      //因为async方法内部包裹的就是Promise,所以这里也return一个Promise
        return new Promise((resolve, reject) => {
          let next = function (data) {
            let { value, done } = iter.next(data);
            if (done) {
              resolve(data);
            } else {
            //兼容b函数是一个非异步操作
            value instanceof Promise?
              value.then(val => {
                next(val);
              }, reject):next(value);
            }
          };
          next();
        });
      }
     let promise = Co(a());
个人理解及总结

迭代器:它的原型上面存在Symbol(Symbol.iterator),它可以被for of 循环
生成器:* 配合yield 的一个函数
如果你搞懂了生成器函数,基于它实现async await就变得很容易了。
目前把es6里面的class Map Set Promise底层实现都写完了

上一篇文章:72行实现一个Promise juejin.cn/post/702689…