Promise.then属于微任务,setTimeout属于宏任务,事件循环中每执行完一个宏任务后必须清空全部微任务队列才取下一个宏任务,因此Promise.then总在同轮事件循环中优先于setTimeout执行。

因为 Promise.then 属于微任务,setTimeout 属于宏任务,而 JavaScript 的事件循环规定:每次执行完一个宏任务后,必须先清空全部微任务队列,再取下一个宏任务。
微任务和宏任务分属不同队列
JavaScript 运行时维护两个独立的任务队列:
- 微任务队列(microtask queue):存放 Promise.then、catch、finally、queueMicrotask、Vue 的 nextTick 等回调
- 宏任务队列(macrotask queue):存放 setTimeout、setInterval、I/O、script 整体代码、用户交互事件等
这两个队列不是并列竞争关系,而是有严格执行次序:每轮事件循环只取一个宏任务执行,结束后立刻批量执行所有已排队的微任务,不中断、不穿插。
执行时机决定优先级高低
同步代码执行完毕后,引擎不会直接跳到 setTimeout 回调,而是先检查微任务队列——哪怕 Promise.then 写在 setTimeout 后面,只要它已就绪,就会立即执行。
- Promise.resolve().then(() => console.log(2)) → 微任务,入队即“待命”
- setTimeout(() => console.log(3), 0) → 宏任务,要等到下一轮事件循环
- 所以输出顺序是 1 → 4 → 2 → 3,而不是按代码书写顺序
Promise 状态变化触发入队时机
Promise.then 是否能“抢在” setTimeout 前执行,取决于它是否已处于 fulfilled 或 rejected 状态:
- Promise.resolve().then(...):构造即 resolve,.then 回调立刻进微任务队列
- new Promise(r => setTimeout(r, 0)).then(...):resolve 发生在 setTimeout 内部,.then 要等那个 setTimeout 执行完才入微任务队列,因此会晚于外层的 setTimeout
关键不在写法先后,而在回调何时被推入哪个队列。
本质是事件循环的设计选择
微任务优先的设计,是为了保障异步操作的响应及时性与一致性:
- 避免 UI 渲染被长时间延迟(比如 Promise 链更新状态后应尽快反映)
- 确保同一轮同步逻辑产生的所有异步副作用(如多个 then)能原子化执行
- 让 async/await 的语义更贴近“同步流程”,减少意外跳转

















