今天继续写在滴滴业务中台前端一面中遇到的事件循环题
题目代码
// 第一个 Promise 链
new Promise((resolve, reject) => {
console.log(1); // 同步输出
resolve(); // 解决 Promise
console.log(2); // 同步输出
})
.then(() => {
console.log(3); // 微任务
})
.then(() => {
console.log(4); // 微任务
setTimeout(() => { // 宏任务
console.log(5); // 宏任务内同步输出
new Promise((resolve) => {
resolve();
})
.then(() => {
console.log(6); // 宏任务内微任务
})
.then(() => {
console.log(7); // 宏任务内微任务链
});
}, 0); // 延迟设为 0
});
// 第二个独立的 Promise 链
new Promise((resolve) => {
resolve();
})
.then(() => {
console.log(8); // 微任务
})
.then(() => {
console.log(9); // 微任务
});
执行结果
代码执行后,控制台输出顺序为: 1, 2, 3, 8, 4, 9, 5, 6, 7
分析
JavaScript 的事件循环(Event Loop)机制决定了代码的执行顺序,主要分为同步任务、微任务(Microtask)和宏任务(Macrotask)。以下是逐步分析:
-
同步代码执行(立即执行):
- 执行第一个
new Promise的构造函数:输出1,调用resolve(),然后输出2。 - 顺序输出:
1, 2。
- 执行第一个
-
微任务队列处理(同步代码执行后,立即处理所有微任务):
-
第一个 Promise 的
resolve()将第一个then回调(输出3)加入微任务队列。 -
第二个 Promise 的
resolve()将第一个then回调(输出8)加入微任务队列。 -
此时微任务队列:
[输出3, 输出8]。 -
处理微任务队列:
- 执行输出
3:输出3。执行后,第一个 Promise 链的下一个then(输出4)被加入微任务队列。队列变为:[输出8, 输出4]。 - 执行输出
8:输出8。执行后,第二个 Promise 链的下一个then(输出9)被加入微任务队列。队列变为:[输出4, 输出9]。 - 执行输出
4:输出4。在执行中,遇到setTimeout,将其回调函数(包含输出5等)加入宏任务队列。执行后,无新微任务。队列变为:[输出9]。 - 执行输出
9:输出9。
- 执行输出
-
微任务队列清空。
-
-
宏任务队列处理(微任务队列空后,处理宏任务):
-
执行
setTimeout的回调函数(延迟为 0,但需等待微任务完成):- 输出
5。 - 内部新 Promise 的
resolve()将then回调(输出6)加入微任务队列。
- 输出
-
宏任务执行后,再次处理微任务队列:
- 微任务队列:
[输出6]。 - 执行输出
6:输出6。执行后,内部 Promise 链的下一个then(输出7)加入微任务队列。 - 执行输出
7:输出7。
- 微任务队列:
-
微任务队列清空。
-
-
最终输出顺序: 同步任务 → 微任务(按添加顺序)→ 宏任务 → 宏任务内产生的微任务。 因此顺序为:
1, 2, 3, 8, 4, 9, 5, 6, 7。
关键点说明:
- 同步优先:
console.log(1)和console.log(2)立即执行。 - 微任务顺序:微任务(如 Promise
then回调)在同步代码后、宏任务前执行,且队列按 FIFO 处理。第一个 Promise 的then先加入队列,但第二个 Promise 的then也在同步阶段加入,因此输出3后输出8。 - 宏任务延迟:
setTimeout即使延迟为 0,也需等待微任务队列清空后才执行。 - 嵌套异步:宏任务(
setTimeout)内产生的微任务,需在宏任务执行后立即处理。