🧑💻 面试官:setTimeout(fn, 0) 会马上执行吗?
🙋♂️ 我:不会,要等同步代码执行完。
🧑💻 面试官:同步代码后面还有一个 Promise 回调,谁先执行?
🙋♂️ 我:先处理排队的微任务,再执行定时器回调。
🧑💻 面试官:如果微任务又不断创建微任务,页面还能及时响应点击吗?await 是把整段代码都变成异步了吗?
这道题要抓住「执行机会」:当前代码什么时候结束,排队的回调什么时候能接着执行,浏览器什么时候才有机会处理其他事情。
面试速答(60 秒版)
浏览器中的 JavaScript,通常先执行当前任务里的同步代码。当前调用栈清空后,会在微任务检查点处理排队的微任务,随后浏览器才有机会渲染、选择下一项任务。
Promise 的后续回调和 await 后面的继续执行,通常进入微任务队列;定时器回调则属于另一项任务。因此,零延迟是尽早安排,不是立刻执行。
async 函数在遇到第一个 await 之前,仍然同步执行。微任务执行过程中新增的微任务,也会继续被处理;如果一直往队列里加任务,就可能拖住渲染和交互。
分析时需要说清当前任务、微任务检查点和下一项任务。Node.js 有自己的调度规则,不能把浏览器例子的顺序直接套过去。

知识点详解:一段代码为什么会按这个顺序输出?
先分清现在执行与安排以后执行
假设页面正在执行一段脚本。console.log 是现在打印,setTimeout 则是向浏览器登记一个将来运行的回调。登记完以后,当前代码继续往下走。
Promise 也需要分开看。new Promise 的执行器立即运行,但 .then 里的回调不会插进当前调用栈。
MDN 执行模型说明了任务与作业的机制。“宏任务”是面试中常用的说法,这里对应浏览器的 task,不代表所有任务来源都共用一个全局队列。
用一个完整例子看 await
下面代码可以放进浏览器控制台。它讨论 JavaScript 机制,不用 Python 的异步代码来冒充相同调度规则。
console.log("A");
setTimeout(() => console.log("B"), 0);
Promise.resolve().then(() => console.log("C"));
async function run() {
console.log("D");
await Promise.resolve();
console.log("E");
}
run();
console.log("F");
// 输出:A、D、F、C、E、B
先打印 A,登记定时器,再登记 Promise 回调。调用 run 时立即打印 D,遇到 await,把后续执行安排出去。回到外层,继续打印 F。
当前脚本结束后,先前排好的微任务开始执行,所以先打印 C,再继续 run,打印 E。随后,定时器取得执行机会,才打印 B。
await 暂停的是这个函数的后续执行,不是让整个浏览器停下来。等待值已经完成,也不意味着后半段立即插队。

微任务为什么可能把页面拖住?
一个微任务运行时又创建了新的微任务,浏览器会在当前检查点继续处理它们。如果程序不断用 queueMicrotask 安排下一次工作,迟迟没有结束,就可能使其他任务和渲染拿不到机会。
所以,把大计算塞进 Promise,不会自动让页面不卡。它仍然可能占用主线程。
可以根据任务分批处理,主动让出执行机会,或者把适合的计算交给 Web Worker。这里的重点是让其他工作有机会执行,不是给函数加一个 async 就宣布完成优化。
每轮结束后,也不一定都会绘制
浏览器是否渲染,还要看刷新时机、页面状态与调度。因此,DOM 已经修改,不等于用户马上看到了新画面。
requestAnimationFrame 适合安排与下一次绘制相关的更新,但不能随意把它塞进“同步、微任务、宏任务”三行口诀,认定任何代码都有固定顺序。
理解了这些条件,面试官换一个例子,你才能重新分析。
面试官继续追问
await 后面不是 Promise,会怎样?
例如 await 1,后续执行仍然是异步继续。不能因为这个值不用等,就把后面的输出判为同步。
setTimeout 写了 100 毫秒,就一定准时吗?
不是。到达时间只是执行资格的条件。如果主线程在忙,回调只能继续等,后台页面也可能有额外限制。
所有任务都是按登记顺序统一排队吗?
不能一概而论。浏览器有不同任务来源及调度规则。可以分析本例,却不能扩展成所有事件都只有一个 FIFO 队列。
面试速记卡
- 当前任务:同步代码先执行,登记回调不等于运行回调。
- 微任务:Promise 后续回调和 await 后续执行通常在这里接上。
- 定时器:零延迟也要等待执行机会。
- 卡顿风险:微任务不断补充,可能拖住交互与渲染。
- 环境边界:浏览器与 Node.js 的规则不能混着背。
