JavaScript事件循环按“宏任务→清空微任务→下一宏任务”推进;宏任务含setTimeout、script等,微任务含Promise.then、queueMicrotask等;微任务在宏任务结束后立即全量执行,故Promise.then总先于setTimeout执行。

JavaScript 的事件循环不是“等所有异步做完再执行”,而是严格按「一个宏任务 → 清空全部微任务 → 下一个宏任务」的节奏推进。理解这点,才能真正预测 setTimeout、Promise.then、queueMicrotask 的实际执行顺序。
怎么一眼分清宏任务和微任务?看注册时机和队列归属
宏任务是事件循环的“调度单位”,每次只取一个;微任务是“插队者”,只要当前宏任务结束,立刻全量执行,不等下一个宏任务。
-
setTimeout、setInterval、script(整个脚本块)、UI 渲染、postMessage回调 —— 都进宏任务队列(Task Queue) -
Promise.then/catch/finally、queueMicrotask、MutationObserver回调 —— 全部进微任务队列(Microtask Queue) -
process.nextTick是 Node.js 独有微任务,浏览器不可用;requestAnimationFrame不是微任务,它属于宏任务,但优先级高于普通setTimeout
为什么 Promise.then 总比 setTimeout 先执行?
因为它们不在同一个队列里,且事件循环规则强制:每个宏任务结束后,必须把当前微任务队列清空,才允许取下一个宏任务。
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
输出一定是:1 → 4 → 3 → 2。即使 setTimeout 延时为 0,它也得排队等下一轮宏任务;而 Promise.then 是微任务,在同步代码(第一个宏任务)执行完后立刻执行。
立即学习“Java免费学习笔记(深入)”;
容易踩的坑:误以为 queueMicrotask 和 setTimeout 只差“快慢”
它们本质不是“快一点 vs 慢一点”,而是“是否让出主线程控制权”。queueMicrotask 不会让浏览器渲染或响应用户输入,适合做 DOM 批量更新前的准备;setTimeout 则明确交还控制权,浏览器可趁机重绘、处理点击事件。
- 想在 DOM 更新后立即读取布局(如
getBoundingClientRect)?用queueMicrotask—— 它在渲染前执行 - 想确保 UI 已刷新、再执行后续逻辑?用
requestAnimationFrame或setTimeout(后者更兼容) - 链式
Promise中嵌套setTimeout,会打断微任务连续性:一旦进入宏任务,就得等完整轮次,中间可能插入用户交互或动画帧
最常被忽略的一点:微任务队列是“动态清空”的——新微任务可在执行中产生(比如 Promise.then 里又调用 Promise.resolve().then),这些新增任务会加入当前轮次的微任务队列末尾,继续执行,直到队列彻底为空。这不是递归,是队列驱动的连续消费。










