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

 async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
} async function async2() {
console.log('async2 start');
return new Promise((resolve, reject) => {
resolve();
console.log('async2 promise');
})
} console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0); async1(); new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
}).then(function() {
console.log('promise3');
});
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都放进去。

Javascript中的Microtask和Macrotask——从一道很少有人能答对的题目说起的更多相关文章

  1. 浅谈JavaScript中闭包

    引言 闭包可以说是JavaScript中最有特色的一个地方,很好的理解闭包是更深层次的学习JavaScript的基础.这篇文章我们就来简单的谈下JavaScript下的闭包. 闭包是什么? 闭包是什么 ...

  2. JavaScript中错误正确处理方式,你用对了吗?

    JavaScript的事件驱动范式增添了丰富的语言,也是让使用JavaScript编程变得更加多样化.如果将浏览器设想为JavaScript的事件驱动工具,那么当错误发生时,某个事件就会被抛出.理论上 ...

  3. JavaScript 中 闭包 的详解

    闭包是什么 在 JavaScript 中,闭包是一个让人很难弄懂的概念.ECMAScript 中给闭包的定义是:闭包,指的是词法表示包括不被计算的变量的函数,也就是说,函数可以使用函数之外定义的变量. ...

  4. 坑:JavaScript 中 操作符“==” 和“===” 的区别

    标题:JavaScript 中 操作符"==" 和"===" 的区别 记录一些很坑的区别: 1. '' == '0' // false 0 == '' // t ...

  5. 在Javascript中闭包(Closure)

    在Javascript中闭包(Closure) 什么是闭包 “官方”的解释是:所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分. ...

  6. javaScript中闭包的工作原理

    一.什么是闭包? 官方”的解释是:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分.相信很少有人能直接看懂这句话,因为他描述的太学术.其实这句话 ...

  7. Javascript中的delete介绍

    关于JavaScript中的Delete一直没有弄的很清楚,最近看到两篇这方面的文章,现对两文中部分内容进行翻译(内容有修改和添加,顺序不完全一致,有兴趣推荐看原文),希望能对大家有所帮助 一.问题的 ...

  8. javascript中的异步 macrotask 和 microtask 简介

    javascript中的异步 macrotask 和 microtask 简介 什么是macrotask?什么是microtask?在理解什么是macrotask?什么是microtask之前,我们先 ...

  9. JavaScript event loop事件循环 macrotask与microtask

    macrotask  姑且称为宏任务,在很多上下文也被简称为task.例如: setTimeout, setInterval, setImmediate, I/O, UI rendering. mic ...

随机推荐

  1. kafka 集群搭建

    环境:ubuntu14.04 版本:jdk1.8,zookeeper 3.4.10,kafka 2.11 搭建步骤: 1. 搭建zookeeper集群 参考链接:zookeeper集群搭建 2. 下载 ...

  2. 通过ELK快速搭建一个你可能需要的集中化日志平台

    在项目初期的时候,大家都是赶着上线,一般来说对日志没有过多的考虑,当然日志量也不大,所以用log4net就够了,随着应用的越来越多,日志散 落在各个服务器的logs文件夹下,确实有点不大方便,这个时候 ...

  3. HTML页面加载异常,按F12调试后居然又好了的解决办法!

    原因: 你的代码中获取数据那一段应该是有console控制台调用的代码,一般应该是console.log之类的,就是因为这句话在没开F12的时候,console是个undefined的东西就卡在那啦. ...

  4. [搜索]ElasticSearch Java Api(一) -添加数据创建索引

    转载:http://blog.csdn.net/napoay/article/details/51707023 ElasticSearch JAVA API官网文档:https://www.elast ...

  5. redis函数总结

    <?php /*1.Connection*/ $redis = new Redis(); $redis->connect('127.0.0.1',6379,1);//短链接,本地host, ...

  6. mysql复习秘籍

    mysql复习 一:复习前的准备 1:确认你已安装wamp 2:确认你已安装ecshop,并且ecshop的数据库名为shop 二 基础知识: 1.数据库的连接 mysql -u -p -h -u 用 ...

  7. UWP 共享文件——发送者

    这一节,顾名思义,即使你要共享数据给别人,你是数据的提供者.分两步即可1.直接复制代码 protected override void OnNavigatedTo(NavigationEventArg ...

  8. SQL Server学习之路(二):主键和外键

    0.目录 1.定义 1.1 什么是主键和外键 1.2 主键和外键的作用 1.3 主键.外键和索引的区别 2.主键(primary key) 2.1 通过SSMS设置主键 2.2 通过SQL语句设置主键 ...

  9. YiShop_如何选择B2C商城开发商

    说到B2C商城开发,现在网上搜索能搜出来一大堆开发公司的信息出来.面对如果巨大的信息用户应该如果去评估和判断哪家更适合自己呢!其实开发B2C商城不仅体现在一家开发公司本身的具体实力,还体现在对每一个商 ...

  10. dom4j详解

    Dom4j下载及使用Dom4j读写XML简介要使用dom4j读写XML文档,需要先下载dom4j包,dom4j官方网站在 http://www.dom4j.org/目前最新dom4j包下载地址:htt ...