之前讲了个手写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)、 .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(' 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(' end');复制代码在浏览器中
- 先执行log,打印出 start
- 然后将promise1的then里面的回调函数存放于microtask queue中
- 再把timeout2的回调函数存放在task queue中
- 执行log,打印出 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中
- 先打印出 start,promise1回调放入microtask queue,timeout2回调放入task queue,打印出 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
但是多试几次,发现结果可能有两种
造成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事件环执行
继续阅读与本文标签相同的文章
Vue.js与MVVM模型的藕断丝连
【译】更多关于渐进式图片加载的实现
-
荷兰将推出全球首个混凝土逐层3D打印住宅区
2026-06-02栏目: 教程
-
探索 JS 中的模块化
2026-06-02栏目: 教程
-
【开源】微信小程序、小游戏以及 Web 通用 Canvas 渲染引擎 - Cax
2026-06-02栏目: 教程
-
浅解前端必须掌握的算法(三):直接插入排序
2026-06-02栏目: 教程
-
【Chrome】Chrome-devtools:对ios-safari移动端的H5页面进行调试(ios-webkit-debug-proxy)
2026-06-02栏目: 教程
