什么是JavaScript事件循环_宏任务与微任务谁先执行?

微任务在当前宏任务结束后、下一个宏任务开始前立即清空执行,故Promise.then总比setTimeout先运行;宏任务典型有setTimeout、I/O、UI渲染等,微任务典型有Promise.then、queueMicrotask、MutationObserver、process.nextTick(Node.js特有,优先级最高)。

微任务在当前宏任务结束后、下一个宏任务开始前立即执行,所以 Promise.then 总是比 setTimeout 先运行。

宏任务和微任务的典型代表有哪些?

区分二者的关键不是“谁更小”,而是浏览器/Node.js引擎定义的**任务分类规则**:

  • 宏任务(Macrotask):setTimeoutsetIntervalsetImmediate(Node.js)、I/O 回调、UI 渲染(浏览器)、postMessage
  • 微任务(Microtask):Promise.then/catch/finallyqueueMicrotaskMutationObserver 回调、process.nextTick(Node.js,优先级高于 Promise)

注意:process.nextTick 是 Node.js 特有,它不属于标准微任务队列,但执行时机比 Promise 更早——它会在当前操作完成后、任何微任务之前执行。

事件循环中它们的执行顺序到底是怎样的?

一次完整的事件循环流程是:

  1. 执行一个宏任务(比如主线程脚本、或某个 setTimeout 回调)
  2. 执行过程中遇到 Promise,其 then 被推入微任务队列;遇到 setTimeout,其回调被推入宏任务队列
  3. 当前宏任务执行完,**立刻清空整个微任务队列**(所有已排队的微任务依次执行,且新加入的微任务也会在这轮被处理)
  4. 然后才从宏任务队列取下一个任务(可能是渲染、也可能是另一个 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 函数返回的是 Promiseawait 后面的表达式一旦 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 却不设退出条件)。