终于弄懂了async/await

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)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,423评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,147评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,019评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,443评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,535评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,798评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,941评论 3 407
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,704评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,152评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,494评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,629评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,295评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,901评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,742评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,978评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,333评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,499评论 2 348

推荐阅读更多精彩内容