登录
首页 >  文章 >  前端

宏任务与微任务优先级解析

时间:2026-05-18 12:37:53 313浏览 收藏

JavaScript事件循环中宏任务与微任务的执行并非简单的“快慢竞争”,而是严格遵循“每个宏任务结束后必须彻底清空全部微任务队列,之后才允许下一个宏任务启动”的硬性调度规则;微任务在宏任务出口处强制、不可中断地批量执行,Promise.then与queueMicrotask同属一轮且按序执行,甚至新生成的微任务也会被纳入本轮清空;async/await本质是Promise.then的语法糖,其后续代码自动落入微任务;而requestAnimationFrame虽为宏任务,却在微任务清空后、渲染前精准触发,专为动画同步设计——理解这一底层秩序,才能真正掌控异步逻辑的时序与UI更新的确定性。

如何识别 宏任务 (Macrotask) 与 微任务 (Microtask) 的优先级抢占逻辑

宏任务和微任务的优先级抢占逻辑,核心不在“谁快谁慢”,而在于事件循环的硬性调度规则:每个宏任务执行完后,必须清空全部微任务队列,一个都不能留,之后才允许取下一个宏任务。这不是抢占,是强制顺序。

微任务总在宏任务出口处“强制清空”

只要当前宏任务(比如脚本主流程、setTimeout 回调、事件处理函数)执行完毕,JS 引擎立刻暂停宏任务调度,转而逐个执行微任务队列里的所有回调,直到队列为空。这个过程不可打断、不可延迟、不看时间戳——哪怕新进来的微任务是在上一个微任务里刚创建的,也得在这轮清空中执行完。

  • Promise.resolve().then(() => console.log('A')) 和 queueMicrotask(() => console.log('B')) 都会进入同一轮微任务队列,按入队顺序执行
  • 即使在 setTimeout 回调里写 Promise.then,它的回调仍属于本轮宏任务结束后的微任务,一定早于下一个 setTimeout
  • 浏览器渲染(UI rendering)发生在微任务清空之后、下一个宏任务开始之前,所以微任务能影响本次渲染前的状态

常见任务类型的明确归属

识别优先级,先分清哪些是宏任务、哪些是微任务。记准典型代表,比死记分类更可靠:

  • 宏任务:script 主流程、setTimeout、setInterval、I/O 回调(如 fetch.then 的上层包装)、DOM 事件(click、input)、requestAnimationFrame(虽时机特殊,但归类为宏任务)、UI 渲染
  • 微任务:Promise.then/catch/finally、async/await 中 await 后的代码、queueMicrotask()、MutationObserver 回调
  • Node.js 特例:process.nextTick 优先级高于所有微任务,它在当前操作结束后、任何微任务之前执行,不属于标准微任务,但实际效果是“超微任务”

async/await 是微任务的“隐形入口”

await 不是暂停执行,而是把后续代码包装成 Promise.then 回调,因此它天然落入微任务队列。这导致看似连续的语句,实际存在明确的异步断点。

  • console.log('start') → 同步
  • await Promise.resolve() → 当前宏任务继续,但 await 后面的代码被推入微任务
  • console.log('after await') → 微任务,在当前宏任务结束后立即执行
  • setTimeout(() => console.log('timeout'), 0) → 宏任务,排在所有微任务之后

requestAnimationFrame 是宏任务里的“特例时间点”

它虽属宏任务,但不进普通宏任务队列,而是由浏览器在下一次重绘前统一触发。执行时机固定在:当前宏任务 + 所有微任务执行完 → 浏览器准备渲染帧之前。这意味着:

  • 它不会插在两个微任务之间,也不会抢在 Promise.then 前面
  • 连续多次调用 rAF,浏览器只保留最后一次,避免重复排队
  • 它适合做动画同步,但不能用来控制异步逻辑顺序——别指望它比 then 先跑

文中关于的知识介绍,希望对你的学习有所帮助!若是受益匪浅,那就动动鼠标收藏这篇《宏任务与微任务优先级解析》文章吧,也可关注golang学习网公众号了解相关技术文章。

资料下载
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>