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

简介:

之前讲了个手写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日
原文作者:卡姆爱卡姆
本文来源 掘金,如需转载请紧急联系作者

相关文章
|
2月前
|
JSON 安全 数据安全/隐私保护
告别密码泄露!Python OAuth与JWT双剑合璧,守护你的数字资产💰
本文探讨了在Python环境中利用OAuth 2.0和JSON Web Tokens (JWT) 提高系统安全性的方法。OAuth 2.0是一种开放标准授权协议,通过用户授权和令牌颁发来保护资源访问。JWT则是一种紧凑、自包含的认证方式,用于安全传输信息。文章详细介绍了如何使用Flask-OAuthlib实现OAuth 2.0认证,以及使用PyJWT生成和验证JWT。结合这两种技术,可以构建出既安全又高效的认证体系,为数据安全提供双重保障。
38 3
|
5月前
|
缓存 监控 API
【微服务战场上的神秘守门人】:揭秘API网关的超能力 —— 探索微服务架构中的终极守护者与它的神奇魔法!
【8月更文挑战第7天】随着微服务架构的流行,企业应用被拆分为围绕特定业务功能构建的小型服务。API网关作为微服务间的通信管理核心,对请求进行路由、认证、限流等处理,简化客户端集成并提升用户体验。以电商应用为例,通过Kong部署API网关,配置产品目录等服务的API及JWT认证插件,确保安全高效的数据交互。这种方式不仅增强了系统的可维护性和扩展性,还提供了额外的安全保障。
62 2
|
5月前
|
安全 数据安全/隐私保护 微服务
微服务 Token 鉴权设计:一场守护系统安全的惊心动魄之战,你敢应战吗?
【8月更文挑战第29天】在微服务架构中,Token鉴权设计至关重要,它通过在客户端与服务器间传递包含用户身份和权限信息的Token来确保系统安全。合理的Token鉴权能有效防止非法访问,保护数据安全。设计时需考虑Token的有效期、刷新机制及加密算法等,以提升安全性。随着技术发展,持续优化鉴权机制对于满足复杂的安全需求至关重要。
76 0
|
5月前
|
JSON API 数据格式
揭秘微服务间沟通的神秘语言!Ruby如何化身“信使”,让服务间通信畅通无阻?
【8月更文挑战第31天】随着应用程序规模的不断扩大,微服务架构因高度模块化、可扩展性和可维护性成为现代软件开发的首选。本文通过示例代码展示如何使用 Ruby 实现微服务间的通信,重点介绍 RESTful API 的应用。首先,通过安装 HTTParty 库简化 HTTP 请求处理;然后创建微服务客户端,演示如何调用用户服务的 API 并获取用户信息;最后,介绍如何解析 JSON 响应,使数据处理更加便捷。这种方式不仅简单高效,还能满足大多数微服务架构下的通信需求。
47 0
|
8月前
|
存储 监控 安全
金石推荐 | 【分布式技术专题】「单点登录技术架构」一文带领你好好认识以下Saml协议的运作机制和流程模式
金石推荐 | 【分布式技术专题】「单点登录技术架构」一文带领你好好认识以下Saml协议的运作机制和流程模式
126 1
|
8月前
|
数据采集 运维 监控
微服务监控:守护系统稳定的终极防线
微服务监控在数字化时代日益重要,它帮助运维和开发人员实时监测服务性能、状态和安全,确保微服务架构的稳定性和可用性。构建微服务监控体系需关注合理监控策略、数据采集处理、可视化及告警。数据采集的三大支柱是指标、日志和链路追踪。监控涵盖基础设施、系统、应用和业务层面。通过优化监控体系、融合业务场景和建立跨团队协作,可提升监控效果。未来,AI和云计算将推动微服务监控向更精准、高效和安全的方向发展。
228 0
|
NoSQL API Redis
微服务轮子项目(04) - 服务认证架构设计(无网络隔离)
微服务轮子项目(04) - 服务认证架构设计(无网络隔离)
133 0
|
负载均衡 API 开发工具
微服务轮子项目(03) - 服务认证架构设计(有网络隔离)
微服务轮子项目(03) - 服务认证架构设计(有网络隔离)
78 0
|
存储 JavaScript 前端开发
去中心化互助公排系统开发指南与方案
区块链技术的最初迭代在某种程度上可与网站第一次进化相媲美
|
开发框架 运维 测试技术
ARBT(阿尔比特)智能合约系统开发稳定版/详细案例/步骤逻辑/需求方案/成熟技术/源码架构
需求分析:与客户充分沟通,了解其业务需求和期望,明确系统的功能和性能要求。