宏任务队列严格遵循fifo原则,按注册顺序线性追加、无优先级插队、不按延迟时间排序,所有入口统一调度,执行受事件循环阶段约束,本质是纯粹的fifo容器。

宏任务队列严格遵循 FIFO(先进先出)原则,但它的“排队”不是简单的时间戳排序,而是由宿主环境统一调度、按注册顺序线性追加的确定性过程——没有优先级插队、不支持动态重排,也没有所谓“变体”。所谓“FIFO 的变体”是一种常见误解。
宏任务入队是纯线性追加,不看延迟值
比如连续调用 setTimeout(fn1, 0)、setTimeout(fn2, 100)、setTimeout(fn3, 0),它们进入宏任务队列的顺序就是调用顺序:fn1 → fn2 → fn3。即使 fn2 延迟更长,也不会“后发先至”。浏览器或 Node.js 不会根据 timeout 时间重新排序,只按插入时机追加到队尾。
- 所有宏任务入口(定时器、事件、fetch 回调等)最终都通过宿主的同一调度路径进入队列
- 队列内部无排序逻辑,只有 append 操作;不存在堆、优先队列或时间轮等结构
-
setTimeout(fn, 0)不等于“立刻”,它只是尽快排队,仍需等待当前宏任务+全部微任务完成
执行节奏受事件循环阶段约束,非单纯队列行为
FIFO 是队列结构属性,但实际执行还取决于事件循环的固定流程:每轮只取一个,且必须完整执行完毕才进入下一阶段。这意味着“排在前面”不等于“马上运行”,中间可能被长任务阻塞,或因渲染、I/O 等隐式阶段延后感知。
- 用户点击触发的
click回调和随后的setTimeout回调,即便注册时间接近,也严格按触发顺序入队 - 若某宏任务执行耗时 200ms,后续所有宏任务都会被顺延,FIFO 依然成立,但响应延迟放大
- 浏览器在宏任务执行后、下一轮开始前,可能插入一次渲染(render frame),但这不属于宏任务,也不影响队列顺序
真正影响“感知公平性”的是调度策略,而非队列本身
原生宏任务队列本身无权重、无抢占、无截止时间——它就是个纯粹的 FIFO 容器。所谓“公平执行”,靠的是上层调度机制,比如:
- 浏览器对高优先级输入事件(如 pointermove)做部分合并或节流,但仍是按处理完成时刻推入宏任务队列
- 开发者可封装
TaskScheduler,统一收口任务提交,在内部按时间戳/类型/权重排序后再批量推入宏任务队列 - Node.js 的
process.nextTick虽常被误认为宏任务,实则属于更高优先级的微任务变体,不参与宏队列竞争
与微任务队列对比,更能看清 FIFO 的纯粹性
微任务队列同样 FIFO,但它在每轮宏任务结束后立即清空,包括执行中动态加入的新微任务——这造成“嵌套微任务仍按注册序执行”的表象,容易让人误以为有优先级机制。而宏任务队列完全不提供这种“即时反馈”,每个任务彼此隔离,彻底刚性排队。
- Promise.then 链产生的多个微任务:注册早的先执行,中途 new Promise().then() 也会排在已有微任务之后
- 但 setTimeout 创建的两个宏任务:无论哪个 Promise 里创建,都只能排在当前宏任务结束后的队尾,绝无例外
- queueMicrotask() 和 Promise.then 共享同一微任务队列;而 setTimeout、setInterval、事件回调各自独立推入宏任务队列,但共享同一个 FIFO 底层结构











