定时器和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,怎么在电脑上显示、操作安卓手机

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

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

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

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

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

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

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

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

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

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

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

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

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

HTML5链接tcpUDP,UDP/TCP协议 网络调试工具源码(C#)

本代码包括了TCP和UDP的客户端和服务端,适合C#初学者学习、参考资源下载此资源下载价格为2D币,请先登录资源文件列表NetWork/NetWork.sln , 990NetWork/NetWork.v12.suo , 27648NetWork/NetWork/App.config , 187NetWork/NetWork/bin/Debug/NetWork.exe …

element 登录_Python selenium自动化测试框架入门实战--登录测试案例

本文为Python自动化测试框架基础入门篇,主要帮助会写基本selenium测试代码又没有规划的同仁。本文应用到POM模型、selenium、unittest框架、configparser配置文件、smtplib邮件发送、HTMLTestRunner测试报告模块结合登录案例实现简单自动化测试框架项目主要包括以下…

后处理安装_Mastercam2017(自定义后处理选项)安装!

我们用Mastercam2017编程的程序,需要用在不同的机床加工零件时,这时候我们也要选择适合不同机床认识的自动后处理文件来生成程序,当然系统默认是不能实现这一功能的,那么安装了自定义后处理选项插件,就能完全实现自定义…

为什么redis取出来是null_[2020] Redis 最新面试题

Redis 的数据类型(数据结构)string (二进制安全,可以存储任意类型的数据)list(链表)字典(就是hashmap)set(不重复无序的hashmap)zset(…

的优先级大小_如何评估需求的优先级?

一、 需求的优先级怎么定义? 很多产品经理,包括我,一定都会遇到这样的场景:“ 需求堆如山,什么都想做 ”。面对各种各样、来自各个渠道的需求,产品经理的工作职责之一,就是梳理需求的优先级。我…

html自动给图片加上水印 代码_如何给一千张图片去水印?还好我会python,100行代码轻松搞定...

写在前面近期好多网友私信我,问我编程该怎么学习、怎么入门。我觉得编程学习,就像写文章一样,需要积累。如果把代码每个字符拆开,大伙都认识,但是组合在一起,就是另外一回事了。所以我的建议是,…

html中两个图片叠放,CSS实现图片叠放(勾选图标)

场景我们经常会遇到这种场景,有一个待选图片列表,在图片上(可能是右上角也有可能时右下角)叠放一个勾选状态图标,这篇文章就记录实现这个功能的过程。原理利用flex布局space-around显示图片列表在图片的外层加一个div,同时把勾选状…

pyspark sparksession_PySpark 处理数据和数据建模

安装相关包from pyspark.sql import SparkSession from pyspark.sql.functions import udf, when, count, countDistinct from pyspark.sql.types import IntegerType,StringType from pyspark.ml.feature import OneHotEncoderEstimator, StringIndexer, VectorAssembler from…

linq查询不包含某个值的记录_MySQL行(记录)的详细操作

阅读目录一 介绍二 插入数据INSERT三 更新数据UPDATE四 删除数据DELETE五 查询数据SELECT六 权限管理一 介绍MySQL数据操作: DML在MySQL管理软件中,可以通过SQL语句中的DML语言来实现数据的操作,包括使用INSERT实现数据的插入UPDATE实现数据的…

听课评课记录计算机应用,教师听课的评语(精选10篇)

教师听课的评语(精选10篇)通过引导学生提炼信息提出问题解决问题,使学生再次感受了数学与现实生活的密切联系,经历了运用乘法口诀求商的计算方法的形成过程,培养了学生对知识的迁移能力。下面是小编整理的教师听课的评语(精选10篇)&#xff0…

java音频实时传输_会议室智能系统建设方案,实时远程视频协作

2019年,预计会议协作需求将持续增长,创建多功能会议室促进本地、异地协作仍然是一个强大的趋势。无论空间大小或距离远近,政府部门、企业单位以及团体组织为了实现决策指令畅通、管理层次分明,需要通过对会议室环境、多功能会议系…

依赖 netty spring_十分钟带你了解Spring的七大知识点,程序员必了解

Spring框架自诞生以来一直备受开发者青睐,有人亲切的称之为:Spring 全家桶。它包括SpringMVC、SpringBoot、Spring Cloud、Spring Cloud Dataflow等解决方案。很多研发人员把spring看作心目中最好的java项目,没有之一。所以这是重点也是难点&…