前端面试题

终于弄懂了async/await

2019-04-27  本文已影响0人  千茉紫依

await可以让JavaScript进行等待,直到一个promise执行并返回它的结果,JavaScript才会继续往下执行。这个特性能让我们以同步的写法来写异步调用。下面来一步步的模拟进行实现。

一、Iterator遍历器介绍
Array、Object都是可以遍历的,但是Set、Map却不能用for循环遍历,为了弥补这个遗憾,es6加入了Iterator遍历器,来为Set、Map、Array、Object新增一个统一的遍历API,这样只要这个对象上面具有[Symbol.iterator]方法,便可以遍历。由于Iterator中next()方法可以传入数组的length进行控制,每次执行next()能返回包含value/done属性的Iterator对象。这使得伪数组中无法被遍历的属性可以被剔除,因而使用es6中新增加的for...of循环来遍历具有[Symbol.iterator]方法的对象,变得更加安全。几乎可以用来全面替代for...in(除非遍历对象时要获取key、value值)。

//next模拟实现:
var it = makeIterator(['a', 'b']);

it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }

function makeIterator(array) {
  var nextIndex = 0;
  return {
    next: function() {
      return nextIndex < array.length ?
        {value: array[nextIndex++], done: false} :
        {value: undefined, done: true};
    }
  };
}

Iterator 具体详解可看此篇:Iterator 和 for...of 循环

二、Generator迭代器介绍
调用 Generator 函数,会返回一个遍历器指针g(想象成空数组就好了),而每个yield相当于一个断点,执行后会有一次返回。如下:

function* gen() {
  yield console.log(1)
  yield console.log(2)
  console.log(3)
}

const g = gen() //遍历器指针
g.next() //1
g.next() //2
g.next() //3

由于yield 的断点返回功能,这使得我们可以使用返回的遍历器临时存储数据,类似于闭包功能,如下:

     function* a() {
            let n = 0;
            while (n < 3) {
                yield n++;
            }
            return;
        }
        let m = a(); //m 为返回的遍历器
        let arr = [];
        for (let s of m) {  //使用for...of遍历
            arr.push(s);
        }
        console.log(arr); //[ 0 ,1 ,2 ]

Generator 具体详解可看此篇:Generator 函数的异步应用

既然yield相当于断点,而await的作用也是让js等待,两者的功能相似,似乎实现await功能有些头绪了,下面看一下需求:去掉promise中的一堆then,实现从原始到后来的过渡

function sleep(time) {  
  return new Promise((resolve, rej) => {
    setTimeout(resolve, time);
  });
}

原始:
sleep(1000).then(()=>{console.log("休眠结束")})
后来:
await sleep(1000)

执行原始函数后,首先返回一个new Promise对象,该Promise被压入microTask数组,在下一个时间片到来时,Promise中的函数执行,setTimeout(resolve, time)被压入macroTask堆,在至少1000ms之后,setTimeout执行,Promise返回,此时触发then函数,resolve被替换成()=>{console.log("休眠结束")},输出"休眠结束"。

这里看出Promise封装了回调函数,避免了回调地狱,但却要写一堆then,感觉依旧比较混乱。下面对sleep进行一层迭代器封装

       function sleep(time) {
            return new Promise((resolve, rej) => {
                setTimeout(resolve, time);
            });
        }
       function* s() {
            yield sleep(1000).then(()=>{console.log(1000);});
            yield sleep(500).then(()=>{console.log(500);});
            yield sleep(200).then(()=>{console.log(200);});
        }
        let m = s();
        console.log(m.next()) 
        // { value: Promise { <resolved> }, done: false }
        // 1000

      let n = s();
      n.next() //200
      n.next() //500
      n.next() //1000

这里先返回了迭代器对象,然后迭代器中的value执行,在resolved之后,输出resolve函数,打印1000。但是在测试3个迭代器时,我们发现数据并没有出现我们预期的结果,这是因为,在yield时返回的promise虽然在microTask中正确排列,但在顺序迭代执行microTask时,setTimeout又被压入macroTask,各个函数从新计算等待时间,从而造成等待异步失败。因此要对迭代器进行异步改写,如下:

n.next().value.then(() => {
      n.next().value.then(()=>{
           n.next()
      });
});

此时输出正常,然后使用深度遍历把他抽象成通用函数:

     function deepScan(iteraor){
            let item=iteraor.next()
            if(item.done){
                return
            }else{
                let {value,done}=item
                if(value instanceof Promise){
                    value.then(()=>{deepScan(iteraor)})
                }else{
                    deepScan(iteraor)
                }
            }
        }

这样,一个使用同步写法的异步功能就实现了,可以把async/await函数近似理解为如下Generator 和Promise的语法糖就可以了。

      //完整代码
       function sleep(time) {
            return new Promise((resolve, rej) => {
                setTimeout(resolve, time);
            });
        }
        function* s() {
            yield sleep(2000).then(() => {
                console.log(1000);
            });
            yield sleep(500).then(() => {
                console.log(500);
            });
            yield sleep(200).then(() => {
                console.log(200);
            });
        }
        function deepScan(iteraor){
            let item=iteraor.next()
            if(item.done){
                return
            }else{
                let {value,done}=item
                if(value instanceof Promise){
                    value.then(()=>{deepScan(iteraor)})
                }else{
                    deepScan(iteraor)
                }
            }
        }
        let n = s();
        deepScan(n)
上一篇 下一篇

猜你喜欢

热点阅读