博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Javascript中的Microtask和Macrotask——从一道很少有人能答对的题目说起
阅读量:6094 次
发布时间:2019-06-20

本文共 2872 字,大约阅读时间需要 9 分钟。

首先我们来看一道题目,如下javascript代码,执行后会在控制台打印出什么内容?

1 async function async1() { 2   console.log('async1 start'); 3   await async2(); 4   console.log('async1 end'); 5 } 6  7 async function async2() { 8   console.log('async2 start'); 9   return new Promise((resolve, reject) => {10     resolve();11     console.log('async2 promise');12   })13 }14 15 console.log('script start');16 setTimeout(function() {17   console.log('setTimeout');18 }, 0);  19 20 async1();21 22 new Promise(function(resolve) {23   console.log('promise1');24   resolve();25 }).then(function() {26   console.log('promise2');27 }).then(function() {28   console.log('promise3');29 });30 console.log('script end');

说实话,真正能在面试中把这道题目答对的前端工程师凤毛麟角。我们先来瞧一下答案吧。把以上代码存到test.js文件中,并用node执行一下,结果如下:

如果把以上代码贴到一个网页中的script标签里面,然后打开这个网页,再打开控制台,可以看到如下输出(Chrome 64位 63.0.3239.84):

结果和node打印的一模一样。那么为什么是这个顺序呢?

 

我们都知道js的单线程特性(html5的web worker不算在内~)以及良好的异步支持。在单线程的前提下,异步任务到底什么时候开始执行,其实是有两个队列来进行管理,即Macrotask和Microtask(只有一个字母的差距,不要认错……)。在当前正在执行的线程中,如果碰到属于Macrotask的异步任务,则放入Macrotask队列;碰到Microtask的异步任务则放入Microtask队列。注意这里只是把任务放入队列,并不会执行它。等到当前主线程任务执行完毕之后,会依次从Microtask队列中取出任务执行,在执行期间当然还是遵循碰到异步任务放入相应队列的原则。等到Microtask任务全部执行过了,此时再从Macrotask队列中取出一个任务执行。

 

属于Macrotask的任务有:

setTimeout,setInteveral,script标签,I/O,UI渲染

属于Microtask的任务有:

Promise,async/await,process.nextTick,Object.observe,MutationObserver

(事实上,即使同样是Microtask,内部也是有优先级的差别的,例如NodeJS的实现上,process.nextTick比Promise要先执行。相关问题可以瞧瞧这个连接:https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/ 。反正我瞧到一半就放弃了,好在async/await和Promise没有优先级差别)

 

然后我们来分析一下本题中的执行顺序:

【1】第15行执行,打印出script start

【2】第16至18行,把回调任务放入Macrotask (目前Macrotask:第16行setTimeout,Microtask:空)

【3】第20行,执行async1函数,先打印出第2行的async1 start

【4】第3行的async2先执行,打印出第8行的async2 start

【5】第9行至第12行遇到Promise,先打印出第11行的async2 promise(注意不管你resolve写在new Promise的函数什么位置,都跟写到最后一句一样!)

【6】第3行的async2返回了Promise,并且async2前面有await修饰,因此后面第4行的任务被放到Microtask(目前Macrotask:第16行setTimeout,Microtask:第4行)

【7】第22至25行,打印出promise1,并把第26行放入Microtask,注意第28行还没执行到,所以这行什么都不做(目前Macrotask:第16行setTimeout,Microtask:第4行,第26行)

【8】第30行打印script end(目前Macrotask:第16行setTimeout,Microtask:第4行,第26行)

【9】脚本主线程执行结束,现在拿出来一个Microtask,即第4行,打印async1 end(目前Macrotask:第16行setTimeout,Microtask:第26行)

【10】再拿出来一个Microtask,即第26行,打印promise2,此时由于第26行后面跟着then,所以把第28行插入Microtask(目前Macrotask:第16行setTimeout,Microtask:第28行)

【11】再拿出来一个Microtask,即第28行,打印promise3(目前Macrotask:第16行的setTimeout,Microtask:空)

【12】Microtask没有了,执行下一个Macrotask,即第16行的setTimeout,打印setTimeout,结束

 

需要注意的是,以下两种写法,效果是一模一样的(resolve的位置无所谓):

写法1:new Promise((resolve, reject) => {  console.log('1111');  resolve();  console.log('2222');});写法2:new Promise((resolve, reject) => {  console.log('1111');  console.log('2222');  resolve();});

 

另外,对于Promise的链式调用,如new Promise(....).then(...).then(...)....,一次只放第一个then的内容进入Microtask,等第一个then执行的时候,会把第二个then放入Microtask,而不是一次把两个then都放进去。

转载于:https://www.cnblogs.com/xuning/p/8117581.html

你可能感兴趣的文章
[C# 网络编程系列]专题六:UDP编程
查看>>
AE中实现Control中的各种图形工具的方法
查看>>
使用cygwin在windows上模拟unix环境,解决不能显示中文的问题
查看>>
No module named urls最新解决方法
查看>>
linux 中 用户管理 (composer 时不能root 遇到)
查看>>
for循环json对象取值
查看>>
EF中Repository模式应用场景
查看>>
单位圆盘的全纯自同构群Aut B(0,1)
查看>>
实验二 Java面向对象程序设计
查看>>
Sicily 1504:Slim Span(最小生成树)
查看>>
(树)Subtrees -- hdu -- 5524
查看>>
------__________________________9余数定理-__________ 1163______________
查看>>
webapp返回上一页 处理
查看>>
新安装的WAMP中phpmyadmin的密码问题
查看>>
20172303 2017-2018-2 《程序设计与数据结构》第5周学习总结
查看>>
11.查询截取分析_慢查询日志
查看>>
(转)HTML的代码(从朋友那转的,看着觉得会有用就转了)
查看>>
eclipse中将一个项目作为library导入另一个项目中
查看>>
Go语言学习(五)----- 数组
查看>>
Android源码学习之观察者模式应用
查看>>