事件循环是JavaScript运行时内建的永不停歇调度模型,决定setTimeout、Promise.then等执行顺序;微任务(如Promise.then)在宏任务后立即清空执行,宏任务(如setTimeout)需等待当前宏任务及所有微任务完成才执行。

事件循环不是你手动开启的机制,而是 JavaScript 运行时(浏览器或 Node.js)内建的、永不停歇的调度模型——它决定 setTimeout、Promise.then、用户点击这些代码到底谁先跑、谁后跑。
为什么 setTimeout(() => console.log(2), 0) 总比 Promise.resolve().then(() => console.log(1)) 晚执行?
因为事件循环每次只取一个宏任务(如 setTimeout 回调),但会在该宏任务结束后、下一个宏任务开始前,**清空整个微任务队列**。
-
Promise.resolve().then(...)是微任务,进微任务队列,紧接在同步代码之后立刻执行 -
setTimeout是宏任务,进宏任务队列,必须等当前宏任务(比如整个脚本)+ 所有微任务都跑完,才轮到它 - 哪怕延迟写
0,浏览器也至少按 ≥4ms 处理;标签页非活跃时可能被节流到 1000ms
async/await 的 await 后面代码,其实是在微任务里执行
await 并不“暂停函数”,而是把后续逻辑自动包装成 Promise.then —— 所以它属于微任务,不是同步,也不是宏任务。
- 这意味着:一个
await Promise.resolve()后的语句,会排在当前宏任务末尾、所有已排队的Promise.then之后,但一定早于下一个setTimeout - 不要指望
await能“让出渲染帧”;需要视觉更新,得用requestAnimationFrame或setTimeout配合强制重排 -
await内部的 Promise 构造器函数(executor)仍是同步执行的,只有resolve()调用才触发微任务入队
什么时候该用 setTimeout(fn, 0),什么时候该用 queueMicrotask(fn)?
两者都能“让出主线程”,但粒度和语义完全不同。
立即学习“Java免费学习笔记(深入)”;
-
setTimeout(fn, 0):推入宏任务队列 → 下一轮事件循环才执行 → 适合需要“真正延后一帧”、避免阻塞渲染或响应输入的场景(比如分片处理大数组) -
queueMicrotask(fn):推入微任务队列 → 当前宏任务结束后立即执行 → 更轻量、无最小延迟限制,适合需紧随当前操作之后执行的逻辑(如 DOM 更新后立即读取 offsetHeight) - 滥用
setTimeout(, 0)会产生大量宏任务,增加调度开销;而queueMicrotask在 Node.js 和现代浏览器中均已原生支持,兼容性足够好
最常被忽略的一点:事件循环本身没有“暂停”或“控制权交还”的 API,你无法干预它的节奏。所谓“异步”,本质是把回调交给运行时环境(浏览器多线程模块或 Node.js libuv)去监听完成信号,再按宏/微任务规则排队——理解这个链条,才能不被 console.log 的顺序骗到。








