从进程线程到 async/await,一文吃透前端异步核心原理定义
一、进程与线程:浏览器的底层基石
基础概念
-
进程:操作系统中正在运行的一个程序实例,是CPU 运行指令时保存和加载上下文所需的时间与资源集合,是操作系统资源分配的最小单位。
-
线程:CPU 执行具体指令所需的最小单位,依附于进程存在,一个进程可以包含多个线程,至少有一个线程

以上以手机为例进行理解:一个手机有一个操作系统,你会安装很多应用程序(app),手机有一定的内存,当你打开一个app时,操作系统就会分配一定的内存空间CPU运行,这个app就是一个进程 -
浏览器中的进程与线程
我们日常使用浏览器多开 Tab 页,本质上就是为每个 Tab 单独创建一个进程,这样做的好处是:
单个 Tab 崩溃不会影响整个浏览器
资源隔离更安全,避免恶意页面窃取其他页面数据
而在每个进程内部,又包含多个关键线程:
渲染线程:负责页面的 HTML、CSS 解析与布局绘制
JS 引擎线程:负责解析和执行 JavaScript 代码
HTTP 请求线程:处理网络请求(如 Ajax、Fetch)
事件触发线程、定时器线程等
JavaScript 可以直接操作 DOM,为了避免 DOM 渲染冲突,渲染线程与 JS 引擎线程必须互斥,不能同时工作。这也是 JS 执行会阻塞页面渲染的根本原因。具体说明如下
1. 浏览器渲染流程分为多个阶段:
- 解析 HTML 构建 DOM 树
- 解析 CSS 构建 CSSOM 树
- 将 DOM 和 CSSOM 合并成渲染树
- 计算布局(重排)
- 绘制页面(重绘)
2. JavaScript 执行时会阻塞 DOM 渲染的原因:
- 因为 JavaScript 可能访问或修改 DOM 属性
- 可能读取或改变 CSS 样式
- 这些操作都需要依赖最新的渲染树状态
3. 互斥机制的具体表现:
- 当 JavaScript 引擎执行时,GUI 渲染线程会被挂起
- GUI 更新会被保存在一个队列中,等待 JavaScript 执行完毕
- 如果 JavaScript 执行时间过长,会导致页面渲染卡顿
4. 优化建议:
- 将 JavaScript 脚本放在 body 底部
- 使用 defer 或 async 属性异步加载脚本
- 避免频繁操作 DOM,使用文档片段或虚拟 DOM
- 复杂计算使用 Web Worker 在后台线程执行
二、V8 引擎:单线程与异步的诞生
V8是Chrome和Node.js所使用的JS引擎,它在执行JS代码时默认只开一个线程。
正是这种单线程特性,催生了JS的异步编程模式
- 遇到同步任务:直接执行。
- 遇到异步任务:先挂起,存入任务队列,等待同步任务执行完毕后再执行异步任务。
这种“先同步,后异步”的执行流程,就是我们常说的事件循环的基础。
三、事件循环:微任务与宏任务
任务分类
在异步任务中,又分微任务与宏任务。
微任务:指在异步任务中耗时更短的任务,优先级更高,会在当前同步代码执行完毕后立即执行
常见的微任务有:
- Promise.then()
- process.nextTick() (Node.js 环境)
- MutationObserver (浏览器环境)
宏任务:指在异步任务中耗时更长的任务,优先级较低,会在微任务全部清空后才会执行
常见的宏任务有:
- 全局 script 代码
- setTimeout() / setInterval()
- AJAX请求、I/O 操作
- UI 渲染(UI-rendering)
完整执行顺序
事件循环机制的执行流程可以总结为 4 步:
先执行同步代码,执行过程中遇到异步任务,将其存入对应的任务队列,微任务存入微任务队列,宏任务存入宏任务队列
同步代码执行完毕后,立即执行微任务队列中的所有任务
微任务全部执行结束后,如有需要则执行页面渲染
渲染完成后,执行宏任务队列中的任务
过程持续循环,直到所有任务都被处理完毕。
四、async/await
ES2017 引入async/await 语法,替代 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捕获。
awaitfn() 会把 fn() 当作同步代码看待,并将await之后的代码加入到微任务队列中,等待当前同步代码和微任务执行完毕后再执行
// 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 之后的代码会被放入微任务队列。
// 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,我们可以了解:
- 浏览器是多进程多线程架构,每个 Tab 是一个独立进程,内部包含渲染线程、JS 引擎线程等 V8 引擎是单线程执行
- JS,因此诞生了异步编程模型 事件循环是 JS异步的核心,通过同步代码优先异步代码,微任务优先于宏任务的执行顺序,保证了异步代码的有序执行
- async/await
理解了这些底层原理,有助于我们更好了解JavaScript中的异步编程,实现更复杂高效的功能。
部分内容参考:https://juejin.cn/post/7618784480570261544
更多推荐

所有评论(0)