Skip to content

异步与事件循环机制深度解析

调度任务体系

  • 进程:计算机调度任务和分配资源的单位
  • 线程:进程中的执行单元,共享进程资源
  • 浏览器多进程模型
    • 主进程:用户界面管理
    • 渲染进程:浏览器内核(JS 引擎、UI 渲染)
    • 网络进程:资源请求处理
    • GPU 进程:图形渲染
    • 插件进程:独立运行插件

渲染进程核心线程

  • 主线程
    • JS 执行与 UI 渲染共用,互斥执行
    • 单线程执行,从上到下顺序处理
  • Web Workers
    • 独立于主线程的工作线程
    • 无法操作 DOM,通过 postMessage 通信
  • 异步任务分类
    • 宏任务:宿主环境提供(setTimeout、事件)
    • 微任务:语言本身提供(Promise.then、MutationObserver)

事件循环机制详解

事件循环机制

执行流程

  1. 同步代码执行

    • 执行 script 脚本
    • 将遇到的异步任务分类:
      • 宏任务:交给浏览器线程计时,计时结束放入宏任务队列
      • 微任务:直接放入微任务队列
  2. 清空微任务队列

    • JS 执行栈清空后,按序执行所有微任务
    • 执行中产生的微任务追加到当前队列尾部
  3. 渲染更新

    • 检查是否需要 DOM 渲染
    • 执行 UI 更新(非必须,由浏览器调度)
  4. 处理宏任务

    • 从宏任务队列取出一个任务执行
    • 执行后回到步骤 2(清空微任务队列)
    • 循环往复

核心规则

  • 每次事件循环
    • 只处理一个宏任务
    • 清空整个微任务队列
  • 队列特性
    • 宏任务队列:每次循环取一个
    • 微任务队列:每次宏任务后创建新队列

宏任务 vs 微任务区别

特性宏任务微任务
触发时机DOM 渲染后DOM 渲染前
示例setTimeout、事件Promise.then
优先级高(紧接同步代码后)

关键结论

微任务执行时机早于宏任务,且在每次宏任务执行后都会清空微任务队列

async/await 原理与实现

核心机制

  1. async 函数

    • 自动返回 Promise 对象
    • 函数内部返回值作为 Promise 的 resolve 值
  2. await 表达式

    js
    async function example() {
      await someAsync();
      console.log("Done");
    }

    等价于:

    js
    function example() {
      return Promise.resolve(someAsync()).then(() => {
        console.log("Done");
      });
    }

与 Promise 关键区别

  • 错误处理
    • 需用 try/catch 捕获异常
    • 任一 await 后的 Promise reject 会导致整个 async 函数中断
js
// 正确错误处理
async function fetchData() {
  try {
    const data = await fetchApi();
    return data;
  } catch (error) {
    console.error("Fetch failed", error);
  }
}

经典执行顺序分析

示例 1:基础事件循环

js
console.log(1);
async function async() {
  console.log(2);
  await console.log(3); // 相当于Promise.resolve(console.log(3)).then(() => {...})
  console.log(4);
}
setTimeout(() => console.log(5), 0);
const promise = new Promise((resolve) => {
  console.log(6);
  resolve(7);
});
promise.then(console.log);
async();
console.log(8);

/* 执行顺序:
同步代码: 1, 6, 2, 3, 8
微任务: 7, 4
宏任务: 5
输出: 1 6 2 3 8 7 4 5 */

示例 2:混合任务执行

js
console.log("main start");

setTimeout(() => {
  console.log("T1:宏任务");
  Promise.resolve().then(() => console.log("T2:微任务"));
});

new Promise((resolve) => {
  console.log("T3:Promise构造");
  setTimeout(() => {
    console.log("T4:宏任务");
    resolve("T6");
    Promise.resolve().then(() => console.log("T5:微任务"));
  }, 300);
}).then(console.log);

/* 执行顺序:
1. 同步代码: 'main start', 'T3:Promise构造'
2. 微任务队列: 空
3. 宏任务队列: [setTimeout0, setTimeout300]

第一轮事件循环:
  输出: main start, T3:Promise构造
  微任务: 空
  宏任务结束

第二轮事件循环 (setTimeout0):
  输出: T1:宏任务
  添加微任务: T2
  清空微任务: T2:微任务

第三轮事件循环 (setTimeout300):
  输出: T4:宏任务
  执行 resolve('T6') → 添加微任务: T6
  添加微任务: T5
  清空微任务队列:
    输出: T6:微任务
    输出: T5:微任务

最终输出:
main start
T3:Promise构造
T1:宏任务
T2:微任务
T4:宏任务
T6:微任务
T5:微任务 */

示例 3:多 Promise 交替执行

js
// Promise链1
Promise.resolve()
  .then(() => console.log(1))
  .then(() => {
    console.log(3);
    return Promise.resolve(7); // 产生2个微任务
  })
  .then(console.log);

// Promise链2
Promise.resolve()
  .then(() => console.log(2))
  .then(() => console.log(4))
  .then(() => console.log(5))
  .then(() => console.log(6))
  .then(() => console.log(8));

/* 执行顺序:
微任务队列变化:
  初始: [微1, 微2]
  第一轮: 输出1,2 → 添加微3,微4
  第二轮: 输出3,4 → 添加微任务(来自Promise.resolve(7))和微5
  第三轮: 输出5 → 添加微6
  第四轮: 输出6 → 添加微8
  第五轮: 输出7
  第六轮: 输出8

输出顺序: 1 2 3 4 5 6 7 8 */

示例 4:经典面试题

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

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

console.log("script start");
setTimeout(() => console.log("setTimeout"), 0);
async1();
new Promise((resolve) => {
  console.log("promise1");
  resolve();
}).then(() => console.log("promise2"));
console.log("script end");

/* 执行顺序:
同步代码:
  'script start'
  'async1 start'(调用async1)
  'async2'(await async2()是同步)
  'promise1'(Promise构造函数同步)
  'script end'

微任务队列:
  async1的await后面的代码 → 微任务1(输出"async1 end")
  Promise的then → 微任务2(输出"promise2")

宏任务队列:
  setTimeout回调

事件循环:
  清空微任务: 输出 "async1 end", "promise2"
  执行宏任务: 输出 "setTimeout"

最终输出:
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout */