定时器和promise_从Promise链理解EventLoop

面试题

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

5a25b02260669dc10a7c0b352b68ff73.png
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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/533753.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ugui源码_UGUI整体解决方案基础篇(Unity 2019)

课程介绍:本课程是UGUI系列课程的第一篇:基础篇主要是讲解UGUI的基础组件及接口的使用方法,目前UGUI是unity最常用的UI系统,这部分基础只是是每个同学都应该掌握的,这里我就是简单的讲解了用法,大家对UGUI熟…

android 实例源码解释,Android Handler 原理分析及实例代码

Android Handler 原理分析Handler一个让无数android开发者头疼的东西,希望我今天这边文章能为您彻底根治这个问题今天就为大家详细剖析下Handler的原理Handler使用的原因1.多线程更新Ui会导致UI界面错乱2.如果加锁会导致性能下降3.只在主线程去更新UI,轮询处理Handl…

amd cpu排行_最新intel和amd处理器性能排行cpu天梯图2019

现在市面上cpu厂家有很多,比如常见的intel系列的、amd系列CPU,cpu对电脑起着至关重要的作用,所以我们需要知道cpu性能的好坏,为此小编这就给大家带来最新intel和amd处理器性能排行对比天梯图,大家可以了解一下吧。inte…

python中的对象列表_Python内建的对象列表

Python内建的对象列表刚写Python肯定会遇到这样的情况,想写些什么,但又不知从何写起...在我看来问题在于我们不知道有什么东东可以拿来玩,这里列出Python的内建对象,稍微归类了一下,多看几遍代码自然笔上生花&#xff…

电脑会显示android,怎么在电脑上显示、操作安卓手机

想要在电脑上显示、操作安卓手机,该怎么办,那么怎么在电脑上显示、操作安卓手机的呢?下面是学习啦小编收集整理的怎么在电脑上显示、操作安卓手机,希望对大家有帮助~~在电脑上显示、操作安卓手机的方法工具/原料windows操作系统安卓手机电脑…

git version是什么软件_Deepin 15.11 安装 ZoneMinder 视频监控软件

Zoneminder是一款开源的视频监控软件,可以很方便的连接ip摄像头。因计划将家中的监控摄像头引入NAS,在一台deepin系统的笔记本是先进行了测试。UBUNTU和debian系统都是很容易安装这个软件的。未来在NAS上用docker启动一个专门的zoneminder,do…

看不出svp补帧_专业补帧软件SVP4 实现PotPlayer视频补帧教程

虽然能实现帧率翻倍,不过现在视频绝大多数都是24帧或25帧,翻倍也才48帧,没办法实现补帧后达到60帧的效果。SVP4是一款专业版视频补帧软件,提供GPU加速,并允许使用中档CPU和几乎任何GPU硬件为60Hz的FullHD 1080p视频重新…

android 通知历史,Android P新特性:追踪应用通知历史

原标题:Android P新特性:追踪应用通知历史IT之家3月9日消息 不久前,谷歌已经正式推出了首个Android P开发者预览版,包含了许多新特性。对此,IT之家也进行了一系列报道。该系统的新特性也正在不断被发现。例如最新消息显…

文件另存为时名称会改变_易经:人处在困境时,不要焦虑,改变固定习惯,就会迎来转机...

我读《易经》,悟到一些规律:人的一生,起起落落,时而顺利,时而受困,都是正常现象,没有必要把困难和压力看得太重。人处在困境时,不要焦虑,只要改变你的固定习惯&#xff0…

ubuntu系统写路由指令_在Ubuntu中如何查看网络路由表详解,

在Ubuntu中如何查看网络路由表详解,什么是Linux中的路由和路由表?路由的过程意味着IP包在网络上从一点传输到另一点。当你向某人发送电子邮件时,你实际上是在将一系列IP数据包或数据报从你的系统传输到另一个人的计算机上。从计算机发送的数据…

jspdf html转换pdf,使用jspdf将HTML转换为pdf时出错

对于一个角度项目,我试图将包含HTML代码的字符串变量转换为pdf文件。我安置了所有的家属,比如:jspdf格式光栅化HTML我的代码如下:b64DecodeUnicode(str) {return decodeURIComponent(atob(str).split().map(function(c) {return % (00 c.charCodeAt(0).toString(16)).slice(-…

澄海哪里学机器人编程_终于发现小孩有必要学机器人编程吗

让孩子学习编程的目的,就像其他教育方式一样,只是希望能帮助孩子找到他的兴趣点,打开孩子的获取知识和能力的大门。一起来看看一篇小孩有必要学机器人编程吗。小孩有必要学机器人编程吗编程和英语类似,属于基本技能,未…

鸿蒙系统替代iOS,华为横空出世!鸿蒙系统,能否替代安卓IOS?

原标题:华为横空出世!鸿蒙系统,能否替代安卓IOS?从长远来看,华为主推鸿蒙系统是必然的选择。毕竟安卓系统为谷歌的,而由于美国限制,让华为格外被动。命运掌握在自己手里,才有足够的话…

ubuntu安装python3.8_将 Ubuntu 16 和 18 上的 python 升级到最新 python3.8 的方法教程

1. 概述 本文记录在 Ubuntu 16.04 上将 python 升级为 3.8 版本,并配置为系统默认 python3 的过程。 在 Ubuntu 16.04 中,python3 的默认版本为 3.5: $ python3 -V Python 3.5.2 本文以在 Ubuntu 16.04 中安装为例,方法同样适用于…

java怎么表示正无穷大_java中怎样表示一个无穷大? 无穷小?

Java中提供了三个特殊的浮点数值:正无穷大、负无穷大、非数,用于表示溢出和出错。正无穷大:用一个正数除以0将得到一个正无穷大,通过Double或Float的POSITIVE_INFINITY表示。负无穷大:用一个负数除以0将得到一个负无穷…

ng bind html 无效,angularjs中ng-bind-html的用法总结

本篇主要讲解angular中的$sanitize这个服务.此服务依赖于ngSanitize模块.(这个模块需要加载angular-sanitize.js插件)要学习这个服务,先要了解另一个指令: ng-bing-html.顾名思义,ng-bind-html和ng-bind的区别就是,ng-bind把值作为字符串,和元素的内容进行绑定,但是ng-bind-htm…

热门搜索怎么实现_三个步骤教你学会,搜索引擎霸屏技术!

做好SEO就要了解搜索引擎霸屏技术,它是在百度中搜索关键字来检索信息。整个画面的推荐都是你的内容。那么客户点击你的可能性就会增加!那么搜索引擎霸屏技术这么好,那要如何做到呢?1.要想成为霸屏,第一步要选择好的关键…

ethtool用法 linux_Linux命令之Ethtool用法详解

Linux/Unix命令之Ethtool描述:Ethtool是用于查询及设置网卡参数的命令。概要:ethtool ethX //查询ethX网口基本设置ethtool –h //显示ethtool的命令帮助(help)ethtool –i ethX //查询ethX网口的相关信息ethtool –d ethX //查询ethX…

html字体如何设置垂直居中显示,css文字水平垂直居中怎么设置?

css文字水平垂直居中怎么设置?下面本篇文章就来给大家介绍使用CSS设置文字水平居中和垂直居中的方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。1、文字水平居中在CSS中想要让文字水平居中,可以使用text-a…

python for循环例子_Python for循环生成列表的实例

Python for循环生成列表的实例 一般Python for语句前不加语句,但我在机器学习实战中看到了这两条语句: featList [example[i] for example in dataSet] classList [example[-1] for example in dataSet] 多方研究和询问,得到如下解释&#…