最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 从Promise链理解Event-Loop

    正文概述 掘金(Kay_)   2020-11-29   446

    面试题

    new Promise(resolve => {
      setTimeout(()=>{
        console.log(666);
        new Promise(resolve => {
         resolve();
        })
        .then(() => {console.log(777);})
      })
      resolve();
     })
     .then(() => {
    	    new Promise(resolve => {
    	      resolve();
    	    })
    	   .then(() => {console.log(111);})
    	   .then(() => {console.log(222);});
     })
     .then(() => {
    	   new Promise((resolve) => {
    	     resolve()
    	   })
    	   .then(() => {
    		     new Promise((resolve) => {
    		       resolve()
    		     })
    		    .then(() => {console.log(444)})
    	  })
    	  .then(() => {
    	    console.log(555);
    	  })
    })
    .then(() => {
      console.log(333);
    })  
    

    答案

    111
    222
    333
    444
    555
    666
    777
    

    如果你没有得出正确的结果,有必要继续往下看.

    为了能正确解答上题,需要对宏任务、微任务以及Event-Loop深入理解.

    知识点

    宏任务

    浏览器执行代码的过程中,JS引擎会将大部分代码进行分类,分别分到这两个队列中--宏任务(macrotask ) 和 微任务(microtask ) .

    常见的宏任务:script(整体代码), XHR回调,setTimeout, setInterval, setImmediate(node独有), I/O.

    上面的描述仍然有些生涩,下面借助案例深入理解.

    app.js

        setTimeout(()=>{ //宏任务2
          console.log(2);
        },0)
        setTimeout(()=>{  //宏任务3
          console.log(3); 
        },0)
        console.log(1);
    

    执行结果: 1 -- 2 -- 3

    • 浏览器开始运行 app.js 时启动了第一个宏任务(宏任务1,指向app.js整体代码)并开始执行.
    • 在执行宏任务1途中遇到了第一个定时器,浏览器便会启用一个新线程去跑定时器的逻辑,而当前的js线程不会停直接跳过定时器继续往下执行.当定时器的那条线程跑完后,它的回调函数被添加到宏任务队列等待,这就是宏任务2.
    • 而js线程这边又遇到了定时器又开启一条线程跑定时器的逻辑,js线程跳过这段继续往下执行.当定时器线程跑完后,它的回调函数被添加到宏任务队列等待,这就形成了宏任务3,宏任务3排在宏任务2的后面.
    • js线程走到最后输出了1,此时宏任务1就结束了.浏览器此刻就会去宏任务队列中寻找,排在最前面的是宏任务2,执行输出2.宏任务2结束又执行宏任务3输出3.

    浏览器里面包含有很多个线程,js是单线程,它跑在js引擎中.而定时器,ajax请求都是在不同的线程里执行.比如定时器线程执行完毕,它会将回调函数放到宏任务队列中.ajax请求也如此,它会单独使用一个线程发起ajax请求,请求成功后将回调函数放入宏任务队列中.这里需要格外指出,ajax请求和定时器本身的延迟功能都不能算是宏任务,当ajax请求以及定时器都执行完毕后,它们的回调函数才会放入宏任务队列里等待执行.

    微任务

    微任务是宏任务的组成部分,微任务与宏任务是包含关系,并非前后并列.如果要谈微任务,需要指出它属于哪个宏任务才有意义.

    常见的宏任务:process.nextTick(nodejs端),Promise.then的回调等.

    app.js

    	console.log(1);
    	new Promise((resolve)=>{
    		resolve();
        }).then(()=>{
    		console.log(2)
        })
    	console.log(3)
    

    执行结果: 1 -- 3 -- 2

    • 运行 app.js 脚本文件启动宏任务1,第一行代码执行输出1.
    • 碰到Promise,将then的回调函数放入宏任务1的微任务队列中等待,线程继续往下.
    • 代码跑到最后一行输出3.此时同步代码执行完毕,开始检查当前宏任务中的微任务队列.
    • 运行微任务队列中的第一个then回调函数输出2.再检查微任务队列,没有发现其他任务.
    • 微任务队列执行完毕,宏任务1执行完毕.

    宏任务通常由宿主环境开启,与此相对应,微任务是 js 引擎从代码层面开启的.

    如果还对宏任务和微任务的关系模棱两可,下面从 Event-Loop 角度详细阐述.

    Event-Loop

    从Promise链理解Event-Loop

    从上图可知,宏任务形成了一个拥有先后顺序的队列.每个宏任务中分为同步代码和微任务队列.

    • 假设js当前的线程执行宏任务1,先执行宏任务1中的同步代码.
    • 如果碰到Promise或者process.nextTick,就把它们的回调放入当前宏任务1的微任务队列中.
    • 如果碰到setTimeout, setInterval之类就会另外开启线程去跑相应的逻辑,而js线程跳过这段继续往下执行.另起的线程执行完毕后再在当前宏任务1的队列后面创建新的宏任务并将定时器的回调函数放入其中.
    • 同步代码执行完,开始执行宏任务1的微任务队列,直到微任务队列的所有任务都执行完.
    • 微任务队列的所有任务执行完毕,宏任务1再看没有其他代码了,当前的事件循环结束.js线程开始执行下一个宏任务,直到所有宏任务执行完毕.如此整体便构成了事件循环机制.

    延伸

    dom操作属于宏任务还是微任务

     console.log(1);
     document.getElementById("div").style.color = "red";
     console.log(2);
    

    在实践中发现,当上面代码执行到第三行时,控制台输出了1并且页面已经完成了重绘,div的颜色变成了红色.

    dom操作它既不是宏任务也不是微任务,它应该归于同步执行的范畴.

    requestAnimationFrame属于宏任务还是微任务

    setTimeout(() => {
      console.log("11111")
    }, 0)
    requestAnimationFrame(() => {
       console.log("22222")
    })
    new Promise(resolve => {
      console.log('promise');
      resolve();
    })
    .then(() => {console.log('then')})
    

    执行结果: promise -- then -- 22222 -- 11111

    很多人会把 requestAnimationFrame 归结到宏任务中,因为发现它会在微任务队列完成后执行.

    但实际上 requestAnimationFrame 它既不能算宏任务,也并非是微任务.它的执行时机是在当前宏任务范围内,执行完同步代码和微任务队列后再执行.它仍然属于宏任务范围内,但是是在微任务队列执行完毕后才执行.

    Promise的运行机制

    包裹函数是同步代码

     new Promise((resolve)=>{
        console.log(1);
    	resolve();
      }).then(()=>{
        console.log(2);
     })
    

    new Promise里面的包裹的函数,也就是输出1的那段代码是同步执行的.而then包裹的函数才会被加载到微任务队列中等待执行.

    Promise链条如果没有return

    new Promise((resolve)=>{
        console.log(1)
    	resolve();
    }).then(()=>{
        console.log(2);
    }).then(()=>{
        console.log(3);
    }).then(()=>{
        console.log(4);
    })
    

    执行结果: 1 -- 2 -- 3 -- 4

    在平时开发中,在Promise链中通常会返回一个新的Promise做异步操作返回相应的值.如下.

    new Promise((resolve)=>{
        console.log(1)
    	resolve();
    }).then(()=>{
         return new Promise((resolve)=>{
           resolve(2)
         })
    }).then((n)=>{
        console.log(n);
    })
    

    执行结果: 1 -- 2

    但上述代码中,then函数的回调里没有返回任何东西.但是后续then包含的回调函数仍然会依次执行,返回 1 -- 2 -- 3 -- 4.并且它可以在末尾无限接then函数,这些函数也都会依次执行.

    多个then函数执行次序

    new Promise((resolve)=>{   // 1
        console.log("a")         // 2         
    	resolve();                // 3
    }).then(()=>{               // 4
        console.log("b");       // 5
    }).then(()=>{               // 6
        console.log("c");       // 7
    })                          // 8
    console.log("d")          // 9
    

    执行结果: a -- d -- b -- c

    • 1,2,3行为同步执行的代码,一气呵成输出 a.
    • 此时线程走到第4行碰到then函数的回调,将其放入微任务的队列等待.
    • 线程继续往后走直接跳到了第9行输出了 d,为什么会忽略第6行的then直接跳到第9行呢?因为第4行的then函数回调执行完毕后才会开始执行第6行的代码.(如果不理解为什么此刻会忽略掉第6行代码可以查阅一下函数柯里化的概念).
    • 同步代码执行完毕,开始执行微任务队列.此时微任务队列里面只包含了一个then的回调函数,执行输出b.
    • 4,5行执行完毕后,开始执行第6行代码.发现了then函数回调,将其放入微任务队列中.此时第一个微任务执行完了,将其清空.
    • 微任务队列中还有一个刚放进去的微任务,执行输出 c.清除此微任务,至此微任务队列为空,全部任务执行完毕.

    解题

    有了以上知识的储备再回到本文最初的面试题,这道题就可以轻松解决了.(为了方便阐述,加入右边行号)

    new Promise(resolve => {            // 1
      setTimeout(()=>{                       // 2
          console.log(666);                   // 3
          new Promise(resolve => {     // 4
            resolve();                              
          })                                          
          .then(() => {console.log(777);})   // 7
      })                                               
      resolve();                                 // 9
     })                                           // 10
     .then(() => {                                // 11
    	     new Promise(resolve => {        // 12
    	       resolve();                              // 13
    	     })
    	     .then(() => {console.log(111);})    // 15
    	     .then(() => {console.log(222);});   // 16
     })                                                 // 17
     .then(() => {                               // 18
    	     new Promise((resolve) => {       // 19
    	       resolve()
    	     })
    	    .then(() => {                               // 22
    		     new Promise((resolve) => {        // 23
    		       resolve()
    		     })
    		    .then(() => {console.log(444)})       // 26
    	     })
    	    .then(() => {                                   // 28
    	       console.log(555);                   // 29
    	    })
    })
    .then(() => {                       // 32
      console.log(333);
    })  
    
    • 线程执行第一行代码,同步执行Promise包裹的函数.
    • 在第二行发现定时器,启动一个宏任务,将定时器的回调放入宏任务队列等待,线程直接跳到第9行执行
    • 第9行执行完开始执行第11行代码发现then函数,放入当前微任务队列中.线程往后再没有可以执行的代码了,于是开始执行微任务队列.
    • 执行微任务队列进入第12行代码,运行到第15行代码时发现then函数放入微任务队列等待.随后线程直接跳到第18行,碰到then函数放到微队列中.后续没有可执行的代码了,再开始执行微任务队列的第一个任务也就是第15行代码输出111.
    • 15行执行完执行到16行碰到then回调放入微任务队列等待.随后线程跳到18行的微任务开始执行,一直执行到22行碰到then函数又放入微任务队列等待.此时线程继续往下跳到第32行碰到then函数放入微任务队列等待.后续没有可执行的代码了,再开始执行微任务队列的第一个任务.
    • 线程跳到第16行执行微任务输出 222,随后又跳到22行执行下一个微任务,在26行处碰到then函数放入微任务队列等待.线程继续执行下一个微任务跳到32行输出 333.至此这一轮的三个微任务全部执行完毕清空,又开始执行微任务队列的第一个任务,线程跳到第26行输出 444.
    • 线程执行到28行碰到then函数回调放入微任务队列等待.后续没有可执行的代码了,再开始执行微任务队列的第一个任务即29行代码输出 555.
    • 所有微任务执行完毕,当前宏任务结束.线程开始执行下一个宏任务,线程跳到第三行输出 666.
    • 线程继续往后第7行碰到then回调放入微任务队列,后续没有可执行的代码了,再开始执行微任务队列的第一个任务输出 777.第二个宏任务执行完毕.

    综上所述:输出分别为 111 -- 222 -- 333 -- 444 -- 555 -- 666 -- 777


    起源地下载网 » 从Promise链理解Event-Loop

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元