宏任务队列仅按FIFO顺序执行已就绪的回调,DOM事件、setTimeout、XHR.onload等各自触发后入队,fetch.then为微任务;微任务总在每个宏任务后立即清空。

宏任务队列本身不“处理”事件、请求或定时器,它只是按顺序存放和执行已就绪的回调。DOM 事件、网络请求(如 fetch 的 then)、setTimeout 等,各自通过不同机制触发并**排队进入宏任务队列**,最终由事件循环统一调度执行。
DOM 事件:同步捕获 + 异步入队
用户点击、键盘输入等 DOM 事件在浏览器底层被同步捕获,但对应的事件回调(如 addEventListener 中的函数)不会立即执行。它们会被包装为宏任务,推入宏任务队列末尾。即使事件立刻发生,也要等当前同步代码执行完、微任务清空后,才从宏任务队列取出执行。
例如:
点击按钮 → 浏览器记录事件 → 当前 JS 执行完 → 清空微任务 → 取出该点击回调执行定时器(setTimeout/setInterval):时间到即入队,不保证准时
定时器由浏览器计时器系统管理。当设定时间到达时,回调**被放入宏任务队列**,而非立刻执行。若此时主线程正忙(比如在运行一个长任务),该回调会等待当前宏任务 + 所有微任务完成后才执行,因此实际延迟 ≥ 设定时间。
立即学习“Java免费学习笔记(深入)”;
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
关键点:
- 多个
setTimeout(fn, 0)会按注册顺序依次入队,不会合并 - 即使设为 0ms,也至少要等本轮事件循环结束
-
setInterval的回调同样每次触发都入宏任务队列,若前一次还没执行完,下一次可能被跳过或堆积(取决于实现)
网络请求(fetch、XMLHttpRequest):响应就绪后入队
fetch 本身返回 Promise,其 .then() 是微任务;但注意:HTTP 响应体真正就绪后触发的回调(如 response.json() 后的链式 .then)仍是微任务。真正进入宏任务队列的是:
-
XMLHttpRequest的onload、onerror回调(属于宏任务) - 使用
async/await时,await fetch(...)后的代码是微任务链的一部分,但整个async函数体外的后续逻辑仍受事件循环节拍约束 - 如果用
fetch+.then+setTimeout包裹回调,则那个setTimeout内部才是宏任务
简单说:网络响应完成 → 触发对应回调 → 若是 XHR.onload 就直接进宏任务队列;若是 fetch().then() 就进微任务队列。
交错触发的真实顺序:看谁先“就绪并入队”
三者没有固定优先级,只看谁先满足条件并成功入队:
- 用户快速点击 + 网络刚好返回 + 定时器同时到期 → 它们几乎同时尝试入队,但入队顺序取决于浏览器内部调度(通常按触发时间粗略排序,但不严格)
- 宏任务队列是先进先出(FIFO),一旦入队,就按顺序执行
- 微任务(如 Promise 回调、
queueMicrotask)总在每个宏任务结束后立即清空,因此会“插队”在宏任务之间
典型交错场景:
同步代码 → 微任务A → 宏任务1(点击)→ 微任务B → 宏任务2(setTimeout)→ 微任务C → 宏任务3(XHR onload)

















