JavaScript事件循环是单线程下严格按序执行任务的规则:同步代码→清空所有微任务→执行一个宏任务→再清空所有微任务→循环往复;Promise.then总比setTimeout先执行,因前者属微任务、后者属宏任务,且事件循环规定每个宏任务结束后必须立即连续执行完当前全部微任务才取下一个宏任务。

JavaScript 事件循环不是“多线程调度器”,而是单线程下严格按序执行任务的规则:同步代码 → 清空所有微任务 → 执行一个宏任务 → 再清空所有微任务 → … 循环往复。
为什么 Promise.then 总比 setTimeout 先执行?
因为它们进的是不同队列,且事件循环强制规定:每个宏任务结束后,必须立刻、连续执行完当前全部微任务,才允许取下一个宏任务。
-
Promise.resolve().then(...)是微任务,进微任务队列(Micro Task Queue) -
setTimeout(..., 0)是宏任务,进宏任务队列(Macro Task Queue) - 即使
setTimeout的延迟是 0,它也得排队等「上一个宏任务结束 + 所有微任务跑完」之后才能轮到
console.log('A');
setTimeout(() => console.log('B'), 0);
Promise.resolve().then(() => console.log('C'));
console.log('D');
// 输出一定是:A → D → C → B
哪些操作属于宏任务?哪些属于微任务?
区分关键不在“快慢”,而在“谁创建”和“何时调度”——宏任务由宿主环境(浏览器/Node.js)注入,微任务由 JS 引擎自身立即排入。
-
常见宏任务:
setTimeout、setInterval、setImmediate(Node.js)、I/O、整体标签内容、UI 渲染(浏览器中) -
常见微任务:
Promise.then/catch/finally、queueMicrotask()、MutationObserver回调、Node.js 中的process.nextTick()
注意:async/await 本质是 Promise 语法糖,await 后面的代码会被包装成微任务,不是“阻塞主线程”,而是“排队等微任务轮次”。
立即学习“Java免费学习笔记(深入)”;
实际开发中容易踩的坑
时序错乱往往不是代码写错了,而是对任务队列的“清空机制”理解不到位。
- 在
setTimeout里改状态,又立刻用Promise.then去读——可能读到旧值,因为then是微任务,执行得比setTimeout回调早 - 连续写多个
.then,误以为会分多次宏任务执行——其实它们都在同一轮微任务中依次执行,中间不会穿插任何宏任务或 UI 渲染 - 滥用
queueMicrotask或无限递归Promise.resolve().then(...),可能引发“微任务风暴”,阻塞 UI 渲染甚至导致页面卡死
最常被忽略的一点:微任务队列是“立即清空”的——哪怕你在某个 Promise.then 里又创建了新的 Promise.then,它也会被加进当前微任务队列,并在本轮就被执行完。这不是“延迟”,而是“紧接”。











