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

简介:

之前讲了个手写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、主线程从任务队列中读取事件,这个过程是循环的

1

四、node事件环

如图(画了好久。。)

1 每一个框 都是一个队列

  • 第一个是定时器(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

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

1 1 这是为什么呢? 我们来调试(by vsCode)一下,在setTimeout和promise上打上共6个断点。 执行后如图所示 1 很明显 代码调试已证明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);
复制代码

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

六、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日
原文作者:卡姆爱卡姆
本文来源 掘金,如需转载请紧急联系作者

相关文章
|
4天前
|
监控 安全 网络安全
解锁Micronaut安全秘籍:一剑封喉,让你的微服务无懈可击,抵御黑客侵袭的终极防护罩!
【9月更文挑战第10天】Micronaut作为高性能、轻量级的云原生Java框架,在微服务开发中备受青睐。本文详细探讨了Micronaut的安全特性,包括认证与授权、通讯加密、服务注册与发现、防火墙与反向代理及日志与监控等,通过示例代码展示了如何配置JWT验证,确保服务间安全通信,增强系统可靠性,并及时发现潜在安全问题。
28 5
|
25天前
|
监控 安全 定位技术
揭秘!你的数据为何赤裸裸暴露在黑客眼中?——物理与环境安全技术,守护信息安全的终极盾牌!
【8月更文挑战第20天】信息安全涵盖网络与数据保护及物理设备安全。物理安全保护实体资产免遭未授权访问或损害,是信息安全根基。常见措施有门禁、监控等。环境安全确保适宜运作条件,如温湿度控制。策略实施需风险评估、设计规划、员工培训等。综上,物理与环境安全对整体信息安全至关重要。
29 1
|
14天前
|
开发者 云计算 数据库
从桌面跃升至云端的华丽转身:深入解析如何运用WinForms与Azure的强大组合,解锁传统应用向现代化分布式系统演变的秘密,实现性能与安全性的双重飞跃——你不可不知的开发新模式
【8月更文挑战第31天】在数字化转型浪潮中,传统桌面应用面临新挑战。本文探讨如何融合Windows Forms(WinForms)与Microsoft Azure,助力应用向云端转型。通过Azure的虚拟机、容器及无服务器计算,可轻松解决性能瓶颈,满足全球用户需求。文中还提供了连接Azure数据库的示例代码,并介绍了集成Azure Storage和Functions的方法。尽管存在安全性、网络延迟及成本等问题,但合理设计架构可有效应对,帮助开发者构建高效可靠的现代应用。
14 0
|
1月前
|
Prometheus 监控 Java
微服务架构下的服务治理策略:打破服务混乱的惊天秘籍,开启系统稳定的神奇之门!
【8月更文挑战第7天】微服务架构将应用细分为可独立部署的小服务,提升灵活性与可扩展性。但服务增多带来治理挑战。通过服务注册与发现(如Eureka)、容错机制(如Hystrix)、监控工具(如Prometheus+Grafana)、集中配置管理(如Spring Cloud Config)和服务网关(如Zuul),可有效解决这些挑战,确保系统的高可用性和性能。合理运用这些技术和策略,能充分发挥微服务优势,构建高效应用系统。
47 1
|
21天前
|
安全 网络安全 数据安全/隐私保护
探索Python中的异步编程:从基础到高级网络安全的守护者:从漏洞到加密,构建坚固的信息防护墙
【8月更文挑战第24天】在本文中,我们将深入探讨Python的异步编程世界。通过逐步介绍基本概念、核心模块以及实际应用示例,我们旨在提供一个全面的理解框架,帮助读者从入门到精通。无论你是初学者还是有经验的开发者,这篇文章都将为你揭示如何利用Python的异步特性来提高程序性能和响应性。 【8月更文挑战第24天】在数字信息的海洋中,网络安全是一艘航向安全的船。本文将带你穿梭在网络的波涛之中,揭秘那些潜藏在水面下的风险与挑战。我们会探索网络漏洞的成因,分析加密技术如何成为数据保护的盾牌,并讨论提升个人和组织的安全意识的重要性。文章旨在启发读者思考如何在日益复杂的网络环境中保护自己的数字身份,同时
|
1月前
|
安全 开发者 Python
跨越编程孤岛,构建互联大陆:深入探索Python进程间通信的奥秘,解锁高效协作新纪元!
【8月更文挑战第3天】在编程领域,Python 因其简洁强大而广受欢迎。但随着项目规模扩大,单进程难以应对复杂需求,此时多进程间的协同就显得尤为重要。各进程像孤岛般独立运行,虽提升了稳定性和并发能力,但也带来了沟通障碍。为解决这一问题,Python 提供了多种进程间通信(IPC)方式,如管道、队列和套接字等,它们能有效促进数据交换和任务协作,使各进程像大陆般紧密相连。通过这些机制,我们能轻松搭建起高效的多进程应用系统,实现更加复杂的业务逻辑。
22 2
|
人工智能 区块链 数据库
开源游戏区块链项目分享:Unity开发的独立区块链
开源游戏区块链项目分享:Unity开发的独立区块 2023年了,区块链在这此时代热浪下都已经是即将燃尽的火苗了,而ChatGPT、Stable Diffusion等AI产品已经成为当下风口和热浪。然而区块链作为上一任浪热下的余晖,真的就这么完事了么?其实目前区块链在国内更多作为信用链存在,用于法律签约、物流运输、商务合作、加密合约等等公共底层方面。 而此文将不仅探讨区块链的其他实际用途,同时也开源了一个Unity3D C#编写的区块链代码,如果你是技术人员,刚好你做区块链项目,希望这个文章和代码能帮助到你。
577 0
|
区块链
阐述佛萨奇开发源码 佛萨奇系统开发原力方案 佛萨奇2.0版本源码部署技术解决逻辑
阐述佛萨奇开发源码 佛萨奇系统开发原力方案 佛萨奇2.0版本源码部署技术解决逻辑
102 0
|
JavaScript Dubbo 小程序
4年工作经验,多线程间的5种通信方式都说不出来,你敢信?
有两个线程,A 线程向一个集合里面依次添加元素“abc”字符串,一共添加十次,当添加到第五次的时候,希望 B 线程能够收到 A 线程的通知,然后 B 线程执行相关的业务操作。线程间通信的模型有两种:共享内存和消息传递,以下方式都是基本这两种模型来实现的。
|
运维 分布式计算 资源调度
走近华佗,解析自动化故障处理系统背后的秘密
集群医生华佗是集群自动化故障监测和处理系统,是平台和运维对接的关键系统,它承担了飞天平台自动化故障处理系统的任务。如何能又快又好地发现和解决线上故障呢?本文为您解析自动化故障处理系统背后的秘密。一起来了解华佗是如何提升集群的故障发现、处理的效率和准确性,解放运维人员,提高飞天稳定性和可靠性的 。
5629 0