面试题

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);})  

答案

111222333444555666777

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

为了能正确解答上题,需要对宏任务、微任务以及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途中遇到了第一个定时器,浏览器便会开启一个新的宏任务2,定时器被添加到宏任务队列等待,线程继续往下执行.•随后又遇到了定时器开启一个新的宏任务3,定时器又被添加到宏任务队列等待,宏任务3排在宏任务2的后面,线程继续往下执行.•线程走到最后输出了1,此时宏任务1就结束了.浏览器此刻就会去宏任务队列中寻找,排在最前面的是宏任务2,发现延迟时间已到允许执行便输出了2,宏任务2结束又执行宏任务3输出3.

宏任务通常是由宿主环境开启.比如在客户端,浏览器就是宿主环境.开始执行一个脚本文件,开启一个定时器任务以及ajax请求,都是浏览器在其底层完成,并非是通过js 引擎去做的这些工作.在服务器端,node就作为了宿主环境.

微任务

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

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

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

Event-Loop

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

•假设js当前的线程执行宏任务1,先执行宏任务1中的同步代码.•如果碰到Promise或者process.nextTick,就把它们的回调放入当前宏任务1的微任务队列中.•如果碰到setTimeout, setInterval之类就会在当前宏任务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})                          // 8console.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_从Promise链理解EventLoop相关推荐

  1. 定时器和promise_分析 Promise 内部实现

    在介绍Promise之前,首先我们举一个栗子,看下面代码 function success (message) {console.log(message)}function fail (message ...

  2. Promise的理解与使用(收藏版)

    1. Promise 是什么? 1.1异步编程 fs 文件操作 require('fs').readFile('./index.html', (err,data)=>{}) 数据库操作 AJAX ...

  3. promise链式调用_这一次,彻底弄懂 Promise

    Promise 必须为以下三种状态之一:等待态(Pending).执行态(Fulfilled)和拒绝态(Rejected).一旦Promise 被 resolve 或 reject,不能再迁移至其他任 ...

  4. 实现同步请求_图解 Promise 实现原理(二)—— Promise 链式调用

    摘要 很多同学在学习 Promise 时,知其然却不知其所以然,对其中的用法理解不了.本系列文章由浅入深逐步实现 Promise,并结合流程图.实例以及动画进行演示,达到深刻理解 Promise 用法 ...

  5. 【Promise学习】Promise的理解

    1.Promise 是什么? 抽象表达: (1) Promise 是一门新的技术(ES6 规范) (2)Promise 是 JS 中进行异步编程的新解决方案 备注:旧方案是单纯使用回调函数 具体表达: ...

  6. 谈谈我对Promise的理解

    一.Promise是什么? Promise是最早由社区提出和实现的一种解决异步编程的方案,比其他传统的解决方案(回调函数和事件)更合理和更强大. ES6 将其写进了语言标准,统一了用法,原生提供了Pr ...

  7. 从如何停掉 Promise 链说起

    在使用Promise处理一些复杂逻辑的过程中,我们有时候会想要在发生某种错误后就停止执行Promise链后面所有的代码. 然而Promise本身并没有提供这样的功能,一个操作,要么成功,要么失败,要么 ...

  8. ES5常用的组合继承及原型链理解

    ES5常用的组合继承及原型链理解 <!DOCTYPE html> <html lang="en"><head><meta charset= ...

  9. 有了async/await,你可以丢掉promise链了

    异步函数可能会一直存在,但有些人认为async/await可能会被抛弃. 为什么? 一个常见的误解是async/await和promise是完全不同的东西. 但其实async/await是基于prom ...

最新文章

  1. 年中盘点:2021年最炙手可热的10家AI初创公司
  2. svn 没有绿色小勾
  3. VS2017 Intelligense C++ 设置的几个重点
  4. 使用python获取CPU和内存信息的思路与实现(linux系统)
  5. 极光推送小结 - iOS
  6. linux 关于目录的命令,Linux ---- 关于目录基本命令
  7. 剑指offer 11.旋转数组的最小数字
  8. java项目加载器_Java程序的类加载器
  9. 亲测可用:推荐一个免费下载外文文献的网站
  10. 弯曲时空量子场论的历史与现状 (上)
  11. js中替换和全部替换
  12. android QQ分享、QQ空间分享
  13. 有什么好用的微信公众号编辑器?快来看看这3款
  14. 增益和偏移的概念_2 理解与校准 ADC 系统的偏移和增益误差
  15. Layer visibleRegion的计算过程
  16. Android 如何优雅的实现控件计时功能
  17. git push报错,
  18. EAUML日拱一卒-微信小程序实战:位置闹铃 (11)-稍微聪明一点
  19. 爬虫手记(scrapy实现断点续爬,文章重点在设置)使用scrapy_redis
  20. SEAM IN ACTION有空就翻翻

热门文章

  1. Windiws环境安装轻量级文件服务器ftpserver
  2. java f.lenth返回值_long length
  3. qt中创建控件布局以及删除原有布局和控件
  4. 设置 Visual Studio 字体/背景/行号 - C语言零基础入门教程
  5. 手把手教你用java完成文件、图片下载
  6. 四则运算计算器c语言switch,设计一个五个数进行四则运算的计算器 c语言
  7. java欧冠抽签,欧冠抽签吐槽:最大的“礼包”被C罗拿走!梅西出局概率超50%?...
  8. mysql tree_MySQL树形遍历(二)
  9. nginx php value,PHP+NGINX参数优化
  10. mysql 查看运行级别_运行级别及进程