Appearance
异步与事件循环机制深度解析
调度任务体系
- 进程:计算机调度任务和分配资源的单位
- 线程:进程中的执行单元,共享进程资源
- 浏览器多进程模型:
- 主进程:用户界面管理
- 渲染进程:浏览器内核(JS 引擎、UI 渲染)
- 网络进程:资源请求处理
- GPU 进程:图形渲染
- 插件进程:独立运行插件
渲染进程核心线程
- 主线程:
- JS 执行与 UI 渲染共用,互斥执行
- 单线程执行,从上到下顺序处理
- Web Workers:
- 独立于主线程的工作线程
- 无法操作 DOM,通过 postMessage 通信
- 异步任务分类:
- 宏任务:宿主环境提供(setTimeout、事件)
- 微任务:语言本身提供(Promise.then、MutationObserver)
事件循环机制详解
执行流程
同步代码执行:
- 执行 script 脚本
- 将遇到的异步任务分类:
- 宏任务:交给浏览器线程计时,计时结束放入宏任务队列
- 微任务:直接放入微任务队列
清空微任务队列:
- JS 执行栈清空后,按序执行所有微任务
- 执行中产生的微任务追加到当前队列尾部
渲染更新:
- 检查是否需要 DOM 渲染
- 执行 UI 更新(非必须,由浏览器调度)
处理宏任务:
- 从宏任务队列取出一个任务执行
- 执行后回到步骤 2(清空微任务队列)
- 循环往复
核心规则
- 每次事件循环:
- 只处理一个宏任务
- 清空整个微任务队列
- 队列特性:
- 宏任务队列:每次循环取一个
- 微任务队列:每次宏任务后创建新队列
宏任务 vs 微任务区别
特性 | 宏任务 | 微任务 |
---|---|---|
触发时机 | DOM 渲染后 | DOM 渲染前 |
示例 | setTimeout、事件 | Promise.then |
优先级 | 低 | 高(紧接同步代码后) |
关键结论
微任务执行时机早于宏任务,且在每次宏任务执行后都会清空微任务队列
async/await 原理与实现
核心机制
async 函数:
- 自动返回 Promise 对象
- 函数内部返回值作为 Promise 的 resolve 值
await 表达式:
jsasync function example() { await someAsync(); console.log("Done"); }
等价于:
jsfunction 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 */