JavaScript事件队列执行顺序疑问:代码预测输出与实际输出不符的原因解析
JavaScript事件队列执行顺序疑问:代码预测输出与实际输出不符的原因解析
首先,咱们先把代码、你的预测和实际输出摆出来,再一步步拆解执行流程,就能精准找到你理解偏差的核心点:
代码与实际输出
你的测试代码:
console.log("Start"); setTimeout(() => console.log("Timeout 1"), 0); async function asyncFunc() { console.log("Async Start"); await new Promise((resolve) => setTimeout(() => resolve(), 0)); console.log("Async End"); } asyncFunc(); Promise.resolve().then(() => console.log("Promise 1")); setTimeout(() => console.log("Timeout 2"), 0); console.log("End");
实际运行输出顺序:
Start Async Start End Promise 1 Timeout 1 Async End Timeout 2
你之前的核心理解偏差
你误以为await后的Async End会直接进入微任务队列,但问题的关键是:只有当await绑定的Promise状态变为resolved后,后续代码才会被推入微任务队列。而你代码里的Promise的resolve是被一个setTimeout宏任务触发的,所以Async End的回调要等那个宏任务执行后,才会进入微任务队列。
先明确两个核心队列的执行规则
- 同步任务:主线程按代码顺序直接执行,优先级最高,所有异步任务都要等同步任务执行完才会被处理。
- 微任务队列(Microtasks):包含
Promise.then/catch/finally、await触发后的回调、queueMicrotask等。每次同步任务执行完毕,主线程会先清空所有微任务队列,再去处理宏任务。 - 宏任务队列(Macrotasks):包含
setTimeout/setInterval、DOM事件、AJAX回调等。每次微任务队列清空后,主线程会取出一个宏任务执行,执行完后又会检查微任务队列,循环往复。
逐行拆解代码执行流程
我们按代码实际执行的顺序一步步跟踪:
- 同步执行
console.log("Start");→ 输出Start。 - 调用
setTimeout(() => console.log("Timeout 1"), 0);→ 把这个回调推入宏任务队列,当前宏任务队列:[Timeout1回调]。 - 定义
asyncFunc函数:同步操作,仅定义函数,不执行内部内容。 - 调用
asyncFunc();:- 同步执行函数内第一行:
console.log("Async Start");→ 输出Async Start。 - 遇到
await new Promise((resolve) => setTimeout(() => resolve(), 0));:- 首先,
new Promise的执行器函数是同步执行的:里面的setTimeout会把resolve()这个回调推入宏任务队列,当前宏任务队列:[Timeout1回调, resolve回调]。 await会暂停asyncFunc的执行,因为当前Promise还是pending状态,所以console.log("Async End");暂时不会进入任何队列,要等Promise变为resolved才会被推入微任务队列。
- 首先,
- 同步执行函数内第一行:
- 回到主线程,执行
Promise.resolve().then(() => console.log("Promise 1"));→Promise.resolve()是立即变为resolved的,所以then的回调直接推入微任务队列,当前微任务队列:[Promise1回调]。 - 调用
setTimeout(() => console.log("Timeout 2"), 0);→ 把这个回调推入宏任务队列,当前宏任务队列:[Timeout1回调, resolve回调, Timeout2回调]。 - 同步执行
console.log("End");→ 输出End。
同步任务全部执行完毕,开始处理微任务队列
当前微任务队列只有[Promise1回调],执行它 → 输出Promise 1,微任务队列清空。
开始处理宏任务队列,按顺序执行每个宏任务,每次执行完一个宏任务就检查微任务队列
- 取出第一个宏任务:Timeout1回调 → 执行它,输出
Timeout 1。执行完后检查微任务队列:为空,继续处理下一个宏任务。 - 取出第二个宏任务:resolve回调 → 执行
resolve(),把之前await绑定的Promise状态改为resolved。此时,await后面的console.log("Async End");被推入微任务队列。- 这个宏任务执行完后,检查微任务队列:现在有
[AsyncEnd回调],执行它 → 输出Async End,微任务队列清空。
- 这个宏任务执行完后,检查微任务队列:现在有
- 取出第三个宏任务:Timeout2回调 → 执行它,输出
Timeout 2。执行完后检查微任务队列:为空,流程结束。
总结你的错误点
你之前错误地认为await后的代码会直接进入微任务队列,但实际上:
- 只有当
await等待的Promise状态变为resolved时,后续代码才会被推入微任务队列。 - 你代码里的Promise的resolve是被一个setTimeout宏任务触发的,所以要等那个宏任务执行后,
Async End才会进入微任务队列,自然会在Promise1和Timeout1之后执行。
备注:内容来源于stack exchange,提问作者Racerr9
相关产品推荐
相关产品推荐

