从进程线程到 async/await,一文吃透前端异步核心原理

事件循环(Event Loop)是 JavaScript 实现单线程非阻塞异步执行的核心机制,也是浏览器与 Node.js 环境中,JS 代码能够有序执行、处理异步任务(网络请求、定时器、DOM 事件等)的底层逻辑。

本文将从进程、线程的基础概念出发,逐步拆解浏览器渲染机制、V8 引擎单线程模型、Event Loop 事件循环,最终落地到 async/await 的原理与实践,帮助前端开发者建立完整的异步编程知识体系。

一、进程与线程:浏览器的底层基石

1. 基础概念

  • 进程 :进程就是操作系统中正在运行的一个程序实例,是CPU 运行指令时保存和加载上下文所需的时间与资源集合,是操作系统资源分配的最小单位
  • 线程 :CPU 执行具体指令所需的最小单位,依附于进程存在,一个进程可以包含多个线程。

2. 浏览器中的进程与线程

我们日常使用浏览器多开 Tab 页,本质上就是为每个 Tab 单独创建一个进程,这样做的好处是:

  • 单个 Tab 崩溃不会影响整个浏览器
  • 资源隔离更安全,避免恶意页面窃取其他页面数据

而在每个进程内部,又包含多个关键线程:

  1. 渲染线程:负责页面的 HTML、CSS 解析与布局绘制
  2. JS 引擎线程:负责解析和执行 JavaScript 代码
  3. HTTP 请求线程 :处理网络请求(如 Ajax、Fetch
  4. 事件触发线程、定时器线程等

⚠️ 核心限制:由于 JavaScript 可以直接操作 DOM,为了避免 DOM 渲染冲突,渲染线程与 JS 引擎线程必须互斥,不能同时工作。这也是 JS 执行会阻塞页面渲染的根本原因。

二、V8 引擎:单线程与异步的诞生

V8是Chrome和Node.js所使用的JS引擎,它在执行JS代码时默认只开一个线程

正是这种单线程特性,催生了JS的异步编程模式

  • 遇到同步任务:直接执行。
  • 遇到异步任务:先挂起,存入任务队列,等待同步任务执行完毕后再执行异步任务。

这种"先同步,后异步"的执行流程,就是我们常说的事件循环的基础。

三、Event Loop:微任务与宏任务

1. 任务分类

在异步任务中,又分微任务与宏任务。

微任务:指在异步任务中耗时更短的任务,优先级更高,会在当前同步代码执行完毕后立即执行

常见的微任务有:

  • Promise.then()
  • process.nextTick() (Node.js 环境)
  • MutationObserver (浏览器环境)

宏任务:指在异步任务中耗时更长的任务,优先级较低,会在微任务全部清空后才会执行

常见的宏任务有:

  • 全局 script 代码
  • setTimeout() / setInterval()
  • AJAX请求、I/O 操作
  • UI 渲染(UI-rendering

2. 完整执行顺序

事件循环机制的执行流程可以总结为 4 步:

  1. 先执行同步代码 ,执行过程中遇到异步任务,将其存入对应的任务队列,微任务存入微任务队列,宏任务存入宏任务队列
  2. 同步代码执行完毕后,立即执行微任务队列中的所有任务
  3. 微任务全部执行结束后,如有需要则执行页面渲染
  4. 渲染完成后,执行宏任务队列中的任务

这个循环会一直持续,直到所有任务都被处理完毕。

四、async/await

async/await 是 ES2017 引入的语法,本质是 Promise 的替代,让异步代码看起来更像同步代码。

核心规则

  • async :函数前加 async,修饰函数(函数声明 / 表达式 / 箭头函数),表示这是一个异步函数 ,等价于函数内部自动返回了一个 Promise 实例对象。

    • 异步函数的返回值会被自动包装成 Promise(即使你返回普通值,也会变成 Promise.resolve(值))。
    • 如果函数内部抛出错误,返回的 Promise 会变成 rejected 状态。
  • await :必须配合 async 使用,只能在 async 函数内部使用,作用是等待 一个 Promise 完成(resolve/reject),如果 await 后面不是 Promise 对象,它就无法 "等待" 该操作完成。

    • 等待期间,JS 引擎会暂停当前 async 函数的执行,去执行其他代码(不会阻塞主线程)。
    • Promise 完成后,await 会返回 Promise 的 resolve 值;如果 Promise 被拒绝(reject),会抛出错误,需要用 try/catch 捕获。
    • await fn() 会把 fn() 当作同步代码看待,并将 await 之后的代码加入到微任务队列中,等待当前同步代码和微任务执行完毕后再执行

代码示例1

javascript 复制代码
// async/await 基础用法 
async function asyncDemo() { 
  console.log('1. async 函数内同步代码'); 
  const res = await Promise.resolve('await 结果'); 
  console.log('3. await 之后的代码(微任务)'); 
  console.log('res:', res);
} 

console.log('0. 全局同步代码'); 
asyncDemo(); 
console.log('2. 全局同步代码结束');

//输出结果:
//0. 全局同步代码
//1. async 函数内同步代码
//2. 全局同步代码结束
//3. await 之后的代码(微任务)
//res: await 结果

上述代码示例表明:async 函数内的同步代码会立即执行,await 之后的代码会被放入微任务队列。

代码示例2

javascript 复制代码
// async/await 处理异步请求 
async function fetchData() {
    try {
        console.log('开始请求数据');
        // 模拟网络请求 
        const response = await new Promise(resolve => {
            setTimeout(() => {
                resolve({ data: '用户信息' });
            }, 1000);
        });
        console.log('请求成功:', response.data);
        return response.data;
    } catch (err) {
        console.error('请求失败:', err);
    }
} 
fetchData().then(data => {
    console.log('最终处理数据:', data);
});
console.log('同步代码继续执行');

运行结果:

可以看到,async/await 让异步代码的写法和同步代码几乎一致,可读性大大提升

五、总结

从进程、线程到 async/await,我们可以了解:

  1. 浏览器是多进程多线程架构,每个 Tab 是一个独立进程,内部包含渲染线程、JS 引擎线程等
  2. V8 引擎是单线程执行 JS,因此诞生了异步编程模型
  3. Event Loop 是 JS 异步的核心,通过同步代码优先异步代码,微任务优先于宏任务的执行顺序,保证了异步代码的有序执行
  4. async/await

理解了这些底层原理,有助于我们更好了解JavaScript中的异步编程,实现更复杂高效的功能。

相关推荐
SuperEugene1 小时前
前端代码注释规范:Vue 实战避坑,让 3 年后的自己还能看懂代码|项目规范篇
前端·javascript·vue.js
进击的尘埃2 小时前
用声明式 YAML Schema 驱动 LLM 做 `Code Review` 自动化
javascript
掘金一周2 小时前
吃龙虾🦞咯!万字拆解OpenClaw的架构与设计 | 掘金一周 3.19
前端·人工智能·后端
kyriewen2 小时前
JavaScript 数据类型全家福:谁是大哥大,谁是小透明?
前端·javascript·ecmascript 6
用户8631263327682 小时前
假设我要实现一个agent群体
前端
凉辰2 小时前
uniapp实现生成海报功能 (开箱即用)
javascript·vue.js·小程序·uni-app
console.log('npc')2 小时前
pnpm使用
前端·npm
OpenTiny社区2 小时前
TinyRobot Skills技巧大公开:让 AI 成为你的 “UI 搭建”副驾驶
前端·vue.js·ai编程