Promise.then 总在 setTimeout 之前执行,因为 Promise.then 是微任务,在当前宏任务结束后立即清空执行;setTimeout 是宏任务,需等待本轮微任务全部执行完毕后才进入下一轮事件循环执行。

任务队列与微任务队列不是并行调度的两个平行通道,而是一个有严格先后次序的嵌套结构:每次宏任务执行完,必须清空全部微任务,才能取下一个宏任务。
宏任务是事件循环的“节拍器”
整个 <script>、setTimeout 回调、用户点击事件、fetch 响应回调,都属于宏任务。它们按入队顺序逐个取出执行,每个只执行一次,且彼此之间必然被“微任务清空周期”隔开。
- 一个宏任务开始执行时,它的同步代码会立即运行(比如 new Promise 内部的 console.log)
- 宏任务内部注册的 Promise.then、queueMicrotask 等,不会立刻执行,而是排队进微任务队列
- 该宏任务的同步部分一结束,引擎立刻转向微任务队列,不暂停、不穿插、不挑拣——把当时所有已存在的微任务按顺序执行完
微任务队列是“即时清算窗口”
它不积累、不跨轮,只服务于当前宏任务结束后的那一小段空档。哪怕你在 .then 里又链式调用 .then,或在 queueMicrotask 里再调一次 queueMicrotask,这些新任务仍算作本轮微任务,全部塞进当前队列尾部,统一在本次清空中执行。
- MutationObserver 回调和 Promise.then 共享同一轮微任务执行权,谁先入队谁先执行,没有优先级差异
- async/await 中 await 后面的代码,本质就是被编译成 Promise.then,因此也进入微任务队列
- 微任务之间没有间隔,但微任务执行完毕后,一定会触发一次 UI 渲染(浏览器行为,非 JS 引擎控制),然后才取下一个宏任务
混合场景下的实际执行节奏
当 setTimeout 和 Promise.then 同时存在,输出顺序不是由“谁写在前面”决定,而是由它们归属的任务类型和插入时机共同决定:
- 同步代码(如 console.log)永远最先执行,属于当前宏任务的一部分
- Promise 构造函数体是同步的,.then 才是微任务;setTimeout 回调一定是宏任务
- 即使两个 setTimeout 都设为 0ms,它们也分属两个不同宏任务,必然被至少一轮微任务清空隔开
- 多个 Promise.then 连续链式调用,只要出自同一个宏任务上下文,就全在同一次微任务阶段跑完,中间不会被任何宏任务打断
关键不是记分类,而是盯住“执行—检查—再执行”节奏
不用死背哪些 API 属于哪一类。遇到一段异步代码,只需问三个问题:
- 这段代码现在是在调用栈里直接运行?→ 那就是同步任务
- 它是不是在 Promise 状态变化后触发(.then/.catch/.finally)、或显式调用 queueMicrotask/MutationObserver?→ 那就是微任务
- 它是不是由 setTimeout/setInterval、事件监听、I/O 完成后推入的回调?→ 那就是宏任务
只要抓住“一个宏任务 → 清空全部微任务 → 下一个宏任务”这个铁律,混合队列的执行逻辑自然就清晰了。


















