嘘🤫!不同环境下宏、微任务的秘密~

简介:

之前讲了个手写Promise,有人提出手写Promise很遗憾不能将promise.then写成microtask,只能用setTimeout来模拟异步,所以这次来讲一下microtask和macrotask

一、task 和 microtask

先理解一下task(宏任务又称macrotask)microtask(微任务)

  • task有setTimeout、setInterval、setImmediate(node,IE10+)、I/O(node)、UI rendering(网页UI View渲染)等
  • microtask有process.nextTick(node)、Promises.then(es6)、 Object.observe(废弃)、 MutationObserver(HTML5监听DOM变化)
  • task 被放置于task queue(宏任务队列);microtask被放置于microtask queue(微任务队列)
  • 在浏览器中,通俗来讲microtask queue 独立于task queue,有microtask就先执行microtask,没有就执行task,详细可阅读这篇文章

二、queue(队列)和stack(栈)

然后了解一下queue和stack

  • queue(队列) 先进先出
  • stack(栈) 函数执行 先进后出 1->2->3依次执行后,从3->2->1释放(销毁)
function one(){
  console.log(1);
  function two(){
    console.log(2);
    function three(){
      console.log(3);
    }
    three();
  } 
  two();
}
one();
复制代码

三、浏览器的事件环

1、所有的同步任务全在主线程上执行,形成一个执行栈

2、主线程之外存在一个执行队列

  • 主线程上的所有异步方法(ajax、setTimeout、setImmedaite、MessageChannel),都会在有结果后(回调),放置(回调函数)在任务队列上

3、主线程(执行栈)任务全部完成后,系统读取任务队列放入执行栈上依次执行

4、主线程从任务队列中读取事件,这个过程是循环的

四、node事件环

如图(画了好久。。)

每一个框 都是一个队列

  • 第一个是定时器(timers)队列(执行完毕或最大数量时【队列清空后】,到下一个阶段)
  • 第二个i/o tcp 流 错误处理(错误回调放入,忽略)
  • 第三个内部队列(忽略忽略)
  • 第四个poll轮询队列(文件操作),一直在这个阶段等待,等定时器到达时间,执行对应的回调
  • 第五个check队列,执行node的task,检查setImmediate
  • 第六个关闭例如tcp的队列(继续忽略)
  • promise.then、process.nectTick之类的microtask,会在当每一个阶段切换时,执行
  • 第一次执行,微任务会先执行一次,典型如proxess.nextTick()

1、

setTimeout(()=>{
  console.log('timeout');
},0);
Promise.resolve().then(data=>{
  console.log('promise1');
});
setImmediate(()=>{
  console.log('immediate')
});
Promise.resolve().then(data=>{
  console.log('promise2');
});
process.nextTick((()=>{
  console.log('nectTick');
}));
复制代码

nectTick -> promise1 -> promise2 -> timeout -> immediate

  • microtask先执行一次,nextTick最先执行,输出nextTick
  • 再执行promise,输出promise1、promise2
  • 执行timers队列,输出timeout
  • 执行check队列,输出immediate

2、

setTimeout(()=>{
  console.log('timeout1');
},0);
setTimeout(()=>{
  Promise.resolve().then(data=>{
    console.log('promise');
  });
  console.log('timeout2');
},0);
process.nextTick((()=>{
  console.log('nectTick');
}));
复制代码

nectTick -> timeout1 -> timeout2 -> promise

因为在setTimeout执行完后到下一个阶段时,执行微任务

五、浏览器事件环和node事件环的区别

console.log('script start');
Promise.resolve().then(data=>{
  console.log('promise1');
  setTimeout(()=>{
    console.log('timeout1');
  },0);
});
setTimeout(()=>{
  console.log('timeout2');
  Promise.resolve().then(data=>{
    console.log('promise2');
  });
},0);
console.log('script end');
复制代码

在浏览器中

  • 先执行log,打印出script start
  • 然后将promise1的then里面的回调函数存放于microtask queue中
  • 再把timeout2的回调函数存放在task queue中
  • 执行log,打印出script end

在队列执行完毕后

  • 我们先执行microtask queue内的函数log,打印出promise1
  • 然后将setTimeout2放入task queue
  • microtask queue还有内容吗?没有了,我们就执行task queue
  • 执行task queue的setTimeout2的回调,打印出timeout2
  • setTimeout2的回调又声明了promise2,我们就把promise2的then存放于microtask queue中

有microtask就执行microtask

  • 所以先执行microtask queue中的回调log,打印出promise2
  • 最后的最后,执行task queue中的回调log,打印出tiemout1

在node中

  • 打印出script start,promise1回调放入microtask queue,timeout2回调放入task queue,打印出script end
  • 然后和浏览器一样执行microtask queue内的函数log,打印出promise1
  • 将setTimeout2放入task queue
  • 执行task queue的setTimeout2的回调,打印出timeout2
  • 将promise2的then存放于microtask queue中

在node中,此时timers执行完了吗?并没有

  • 所以执行task queue的回调log,打印出timeout1
  • 最后在队列切换(timers切换到下一队列)时,执行microtask queue的回调log,打印出promise2

但是多试几次,发现结果可能有两种

这是为什么呢? 我们来调试(by vsCode)一下,在setTimeout和promise上打上共6个断点。 执行后如图所示
很明显 代码调试已证明timeout1先于promise2的then的回调函数执行

造成timeout1和promise2的原因是无法比较then的回调和timeout1的执行速度,所以这么写是没有意义的。 如以下代码

Promise.resolve().then(data => {
  console.log('p1');
  setTimeout(() => {
    console.timeEnd('t1');
  },0);
});
setTimeout(() => {
  console.log('t2');
  console.time('t1');
  console.time('p2');
  Promise.resolve().then(data => {
    console.timeEnd('p2');
  });
},0);
复制代码

结果只和执行速度有关,没有可比性

六、node的其他task

node中的task有setTimeout、setInterval、setImmediate、I/O等

下面是个经典面试题

const fs = require('fs');
fs.readFile('./1.txt','utf8',(err,data)=>{
    console.log('success');
    setTimeout(()=>{
       console.log('timeout'); 
    },0);
    setImmediate(()=>{
        console.log('immediate');
    });
})
复制代码

这段代码执行顺序为 success -> immediate -> timeout。

因为在node事件环内在fs所在的poll队列后,只能执行check队列(setImmediate),只有在下一个循环,才走timers队列。

七、总结

1、浏览器中,有microtask,优先执行microtask,没有就执行task。

2、node中,由于node事件环,microtask只在改变队列时执行。task按照node事件环执行




原文发布时间:2018年06月29日
原文作者:卡姆爱卡姆
本文来源 掘金,如需转载请紧急联系作者

相关文章
|
3月前
|
监控 安全 网络安全
解锁Micronaut安全秘籍:一剑封喉,让你的微服务无懈可击,抵御黑客侵袭的终极防护罩!
【9月更文挑战第10天】Micronaut作为高性能、轻量级的云原生Java框架,在微服务开发中备受青睐。本文详细探讨了Micronaut的安全特性,包括认证与授权、通讯加密、服务注册与发现、防火墙与反向代理及日志与监控等,通过示例代码展示了如何配置JWT验证,确保服务间安全通信,增强系统可靠性,并及时发现潜在安全问题。
55 5
|
4月前
|
Prometheus 监控 Java
微服务架构下的服务治理策略:打破服务混乱的惊天秘籍,开启系统稳定的神奇之门!
【8月更文挑战第7天】微服务架构将应用细分为可独立部署的小服务,提升灵活性与可扩展性。但服务增多带来治理挑战。通过服务注册与发现(如Eureka)、容错机制(如Hystrix)、监控工具(如Prometheus+Grafana)、集中配置管理(如Spring Cloud Config)和服务网关(如Zuul),可有效解决这些挑战,确保系统的高可用性和性能。合理运用这些技术和策略,能充分发挥微服务优势,构建高效应用系统。
62 1
|
4月前
|
安全 网络安全 数据安全/隐私保护
探索Python中的异步编程:从基础到高级网络安全的守护者:从漏洞到加密,构建坚固的信息防护墙
【8月更文挑战第24天】在本文中,我们将深入探讨Python的异步编程世界。通过逐步介绍基本概念、核心模块以及实际应用示例,我们旨在提供一个全面的理解框架,帮助读者从入门到精通。无论你是初学者还是有经验的开发者,这篇文章都将为你揭示如何利用Python的异步特性来提高程序性能和响应性。 【8月更文挑战第24天】在数字信息的海洋中,网络安全是一艘航向安全的船。本文将带你穿梭在网络的波涛之中,揭秘那些潜藏在水面下的风险与挑战。我们会探索网络漏洞的成因,分析加密技术如何成为数据保护的盾牌,并讨论提升个人和组织的安全意识的重要性。文章旨在启发读者思考如何在日益复杂的网络环境中保护自己的数字身份,同时
|
7月前
|
机器学习/深度学习 人工智能 安全
安全多方计算之六:秘密共享
安全多方计算之六:秘密共享
|
Web App开发 安全 前端开发
「隐语小课」可防御侧信道攻击的 TEE 运行时环境 DOVE 解读
「隐语小课」可防御侧信道攻击的 TEE 运行时环境 DOVE 解读
316 0
|
JavaScript Dubbo 小程序
4年工作经验,多线程间的5种通信方式都说不出来,你敢信?
有两个线程,A 线程向一个集合里面依次添加元素“abc”字符串,一共添加十次,当添加到第五次的时候,希望 B 线程能够收到 A 线程的通知,然后 B 线程执行相关的业务操作。线程间通信的模型有两种:共享内存和消息传递,以下方式都是基本这两种模型来实现的。
|
存储 弹性计算 安全
谈AK管理之进阶篇 - 如何有效控制云上[最后一把密钥]的风险?
上一期“谈AK管理之基础篇”,我们讲了如何规范的进行访问密钥生命周期管理。通过分出不同权限的阿里云RAM子账号,将不同的权限分给不同的用户,这样一旦子账号泄露也不会造成全局的信息泄露。但是,由于子账号在一般情况下是长期有效的,因此,子用户的访问密钥也是不能泄露的。
谈AK管理之进阶篇 - 如何有效控制云上[最后一把密钥]的风险?
|
前端开发 JavaScript Serverless
如何“取巧”实现一个微前端沙箱?
如今微前端已经成为前端领域比较火爆的话题,在技术方面,微前端有一个始终绕不过去的话题就是前端沙箱。本文将分享阿里云开放平台微前端方案的沙箱实现原理,具体探讨在微前端领域如何实现前端沙箱。(文末免费下载《2020 前端工程师必读手册》电子书)
3475 0
如何“取巧”实现一个微前端沙箱?
|
Java 中间件 Serverless
不改代码也能全面 Serverless 化,阿里中间件如何破解这一难题?
Serverless 话题涉及范围极广,几乎包含了代码管理、测试、发布、运维和扩容等与应用生命周期关联的所有环节。
7358 0
|
运维 分布式计算 资源调度
走近华佗,解析自动化故障处理系统背后的秘密
集群医生华佗是集群自动化故障监测和处理系统,是平台和运维对接的关键系统,它承担了飞天平台自动化故障处理系统的任务。如何能又快又好地发现和解决线上故障呢?本文为您解析自动化故障处理系统背后的秘密。一起来了解华佗是如何提升集群的故障发现、处理的效率和准确性,解放运维人员,提高飞天稳定性和可靠性的 。
5678 0
下一篇
无影云桌面