事件循环

6 阅读3分钟

JS是一门单线程语言,意味着js引擎只能同步地执行代码,一些高耗时的操作会带来进程阻塞的问题。为了避免阻塞,提出了事件循环的机制。

微任务与宏任务

在JS中,所有的任务都可以分为同步任务(立即进入到主进程中执行)和异步任务两种。
而异步任务又可以分为宏任务微任务两种,宏任务由宿主发起,微任务由js自身发起。
顾名思义宏任务就是体量比较大的任务,无法直接执行,需要进行拆分。比较常见的宏任务有定时器、I/O流、setImmediate(Node环境)、script代码块等。常见的微任务有Promise 的回调函数(then、catch、finally)、MutationObserver监听函数、nextTick(Node环境)。

事件循环 even loop

任务具体的执行顺序是一个循环:

  • 执行一个宏任务,如果遇到同步任务就立即执行,遇到微任务就放到微任务队列中,遇到宏任务就放到宏任务队列中
  • 当前宏任务执行完毕后,循环检查微任务的事件队列,依次执行其中的微任务
  • 当微任务队列也为空时,执行下一个宏任务,再次进入循环

async await

这是es6对promise的语法糖,成套使用。async声明这个任务是异步的,await下面的代码是异步微任务,类似promise.then。当遇到async函数时,当作同步任务执行,直到碰到await,把await下面的代码阻塞,放到微任务队列中,然后跳出这个函数执行后续的代码。

以下是deepseek生成的演示代码

console.log("[1] 同步代码开始");

// 宏任务 (MacroTask)
setTimeout(() => {
  console.log("[7] 宏任务1 (setTimeout)");
  Promise.resolve().then(() => console.log("[8] 微任务4 (宏任务1中的Promise)"));
}, 0);

// 微任务 (MicroTask)
Promise.resolve().then(() => {
  console.log("[4] 微任务1 (Promise.then)");
});

// async函数中的微任务
async function asyncDemo() {
  console.log("[2] async函数内部同步代码");
  await Promise.resolve(); // 隐式创建微任务
  console.log("[5] async函数内部微任务 (await后)");
  setTimeout(() => console.log("[9] 宏任务2 (async内部setTimeout)"), 0);
}

asyncDemo();

// 另一个微任务
Promise.resolve().then(() => {
  console.log("[6] 微任务2 (Promise.then)");
});

console.log("[3] 同步代码结束");

// [1] 同步代码开始
// [2] async函数内部同步代码
// [3] 同步代码结束
// [4] 微任务1 (Promise.then)
// [5] async函数内部微任务 (await后)
// [6] 微任务2 (Promise.then)
// [7] 宏任务1 (setTimeout)
// [8] 微任务4 (宏任务1中的Promise)
// [9] 宏任务2 (async内部setTimeout)

拓展

随着现代web技术的发展,一个宏任务队列已经无法满足需求了,浏览器和node都是拥有不止一个队列的。但毕竟我们只是为了应付面试,就不细究了,具体队列和它们的优先级和案例请看这篇文章吧。【Event Loop】浏览器与 Node.js 事件循环详解事件循环是宿主环境处理 JS 异步操作,让其能够非阻塞式运 - 掘金