JavaScript异步顺序靠任务分类、优先级和事件循环节奏保障;微任务总在宏任务前执行,且每轮只取一个宏任务并立即清空全部微任务。

JavaScript 保证异步代码顺序执行,靠的不是“强制排队”,而是任务分类 + 执行优先级 + 事件循环的固定节奏。关键不在于你写得“多有序”,而在于你用的是哪类任务、它们被放进哪个队列、以及事件循环怎么取任务。
微任务总比宏任务先跑完
这是最核心的一条规则。只要同步代码一结束,JS 引擎立刻清空当前所有微任务,一个不落、按注册顺序依次执行,中途不会插入任何宏任务。
- Promise.then()、queueMicrotask()、MutationObserver 回调都进微任务队列
- setTimeout、setInterval、I/O、UI 渲染等属于宏任务,进另一个队列
- 哪怕 setTimeout 设为 0ms,它的回调也得等所有微任务跑完才轮到
每次事件循环只取一个宏任务
宏任务队列是“一轮一取”,不是“一股脑全上”。每轮事件循环只从宏任务队列里拿出最老的一个执行,执行完立刻检查微任务队列——不是等下一圈才看。
- 比如:script(初始宏任务)→ 执行完 → 清空全部微任务 → 下一轮 → 取 setTimeout 回调(下一个宏任务)→ 再清空微任务
- 这就解释了为什么连续两个 setTimeout(…, 0),第二个总在第一个之后执行,哪怕时间相同
想让多个异步操作严格串行,得靠 await 或链式 Promise
事件循环本身不管“逻辑顺序”,它只管“任务类型和队列”。你要控制多个异步步骤的先后,得在代码结构上下功夫:
立即学习“Java免费学习笔记(深入)”;
- 用 for...of + await:每次迭代都等前一个 Promise settled 后再进下一轮,天然保序
- 用 Promise 链:.then(() => fn1()).then(() => fn2()),每个 then 返回的新 Promise 构成依赖链条
- 避免直接 for 循环里写 setTimeout 或 .then,那样会并发注册,顺序由执行完成时间决定,不可控
浏览器渲染也参与事件循环节奏
UI 渲染本身是一个宏任务,而且浏览器通常把它放在宏任务队列末尾或单独调度。这意味着:
- 你在一次宏任务里多次修改 DOM,浏览器不会逐次重绘,而是等这轮宏任务+所有微任务都结束后,统一渲染一次
- 想强制触发重排重绘?可以用 queueMicrotask(() => { /* 此时 DOM 已更新,但还没渲染 */ }) 或者 setTimeout(() => { /* 渲染后执行 */ }, 0)


















