微任务在当前宏任务结束后、下一个宏任务开始前立即清空执行,故Promise.then总比setTimeout先运行;宏任务典型有setTimeout、I/O、UI渲染等,微任务典型有Promise.then、queueMicrotask、MutationObserver、process.nextTick(Node.js特有,优先级最高)。
微任务在当前宏任务结束后、下一个宏任务开始前立即执行,所以 Promise.then 总是比 setTimeout 先运行。
区分二者的关键不是“谁更小”,而是浏览器/Node.js引擎定义的**任务分类规则**:
setTimeout、setInterval、setImmediate(Node.js)、I/O 回调、UI 渲染(浏览器)、postMessage
Promise.then/catch/finally、queueMicrotask、MutationObserver 回调、process.nextTick(Node.js,优先级高于 Promise)注意:process.nextTick 是 Node.js 特有,它不属于标准微任务队列,但执行时机比 Promise 更早——它会在当前操作完成后、任何微任务之前执行。
一次完整的事件循环流程是:
线程脚本、或某个 setTimeout 回调)Promise,其 then 被推入微任务队列;遇到 setTimeout,其回调被推入宏任务队列setTimeout)这意味着:哪怕你在微任务里又创建了新的微任务,它也会被本轮执行掉,不会等到下一轮宏任务之后。
console.log(1); setTimeout(() => console.log(2), 0); Promise.resolve().then(() => console.log(3)); Promise.resolve().then(() => console.log(4)); console.log(5); // 输出:1 → 5 → 3 → 4 → 2
async/await 看起来像同步,实际还是微任务?async 函数返回的是 Promise,await 后面的表达式一旦 resolve,后续代码会被包装进 Promise.then —— 所以它本质仍是微任务调度。
await 不会阻塞线程,只是让出控制权,等 Promise settled 后再把后续逻辑排进微任务队列await 链式调用,每一步 resolve 后都会触发一次微任务排队await 的是普通值(非 Promise),V8 会自动包装成 Promise.resolve(value),仍走微任务流程console.log('start');
async function foo() {
console.log('before await');
await Promise.resolve();
console.log('after await');
}
foo();
console.log('end');
// 输出:start → before await → end → after await
真正容易被忽略的是:微任务队列在每次宏任务后被**完全清空**,而不是只执行一个。这个“清空”行为会让嵌套的 Promise.then 或连续的 queueMicrotask 表现出极强的连续性,甚至可能引发栈溢出风险(比如递归调用 queueMicrotask 却不设退出条件)。