JavaScript事件循环_微任务与宏任务解析

宏任务先执行,微任务在宏任务后立即清空队列,async/await中await后续代码作为微任务执行,setTimeout属于宏任务延后执行。

JavaScript 是单线程语言,通过事件循环(Event Loop)实现异步操作的管理。理解微任务与宏任务的执行机制,是掌握 JS 异步编程的关键。

宏任务与微任务的基本概念

宏任务(Macrotask):每次事件循环中执行的一个完整任务单元,比如 script 全局代码、setTimeout、setInterval、I/O、UI 渲染等。

微任务(Microtask):在当前任务结束后、下一轮事件循环开始前立即执行的任务,比如 Promise.then、MutationObserver、queueMicrotask 等。

事件循环的流程是:执行一个宏任务 → 执行所有可执行的微任务 → 进入下一个宏任务。

执行顺序:微任务优先于下一个宏任务

每当一个宏任务执行完毕,JS 引擎会清空当前所有的微任务队列,再进入下一个宏任务。

这意味着即使微任务是在宏任务中间创建的,也会在该宏任务结束后立刻执行。

例如:

console.log('1');

setTimeout(() => {
  console.log('2');
}, 0);

Promise.resolve().then(() => {
  console.log('3');
});

console.log('4');

输出顺序为:1 → 4 → 3 → 2

  • 全局 script 是第一个宏任务
  • 同步代码先输出 1 和 4
  • Promise 的 then 回调进入微任务队列,宏任务结束后执行,输出 3
  • setTimeout 属于宏任务,进入下一轮事件循环,最后输出 2

async/await 与微任务的关系

async 函数中的 await 后续代码会被包装成微任务。

看这个例子:

async function async1() {
  console.log('async1 start');
  await async2();
  console.log('async1 end');
}

async function async2() {
  console.log('async2');
}

console.log('script start');
async1();
console.log('script end');

输出为:script start → async1 start → async2 → script end → async1 end

  • async1 执行输出 'async1 start'
  • await 调用 async2,其内部同步代码执行,输出 'async2'
  • await 后面的代码被包装成微任务,所以 'async1 end' 在当前宏任务结束后才执行

常见误区与注意事项

很多人误以为 setTimeout(fn, 0) 会立即执行,实际上它只是将回调加入下一个宏任务队列,必须等待当前所有微任务完成。

微任务执行时机非常“紧迫”,适合用于需要尽快响应的操作,如 Promise 链式调用、状态变更通知等。

避免在微任务中无限递归,比如连续调用 queueMicrotask,可能导致主线程阻塞,无法进行 UI 渲染或其他异步操作。

基本上就这些。搞清楚宏任务和微任务的层级关系,就能准确预测 JS 代码的执行顺序。不复杂但容易忽略细节。