微任务队列中新产生的微任务会立即追加到当前队列尾部,并在本轮事件循环中持续执行直至清空。例如promise.then或queuemicrotask中创建的新微任务均属本轮,而宏任务无论何时注册都必须等待下一轮。

微任务队列里新产生的微任务,会立刻加到当前队列尾部,并在本轮事件循环中继续执行,直到队列彻底清空。
新微任务属于“本轮”而非“下一轮”
只要当前微任务队列还没清空,任何在执行过程中新加入的微任务(比如在 Promise.then 里又调用 Promise.resolve().then() 或 queueMicrotask()),都会被追加到队尾,接着被引擎取出执行。这跟宏任务完全不同——宏任务哪怕是在微任务里创建的,也必须排队等下一轮事件循环。
- 例如:
Promise.resolve().then(() => { console.log('A'); Promise.resolve().then(() => console.log('B')); });输出一定是 A → B,中间不会插入任何宏任务 - 再比如:
queueMicrotask(() => { console.log('1'); queueMicrotask(() => console.log('2')); });也会连续输出 1 → 2
执行过程是“递归式清空”,不是“只跑一遍”
事件循环对微任务队列的处理逻辑不是“遍历一次就停”,而是持续检查:执行一个,看队列是否还非空;如果还有,就取下一个,如此反复,直到队列长度变为 0。这个过程发生在同一个宏任务结束后、下一个宏任务开始前的间隙。
- 这意味着嵌套越深、链式越长,微任务队列可能越庞大,但全部都在当前轮次完成
- 极端情况下(如无限链式
Promise.then),会导致主线程长时间阻塞,无法响应 UI 或处理其他宏任务
和宏任务的关键区别就在这里
宏任务无论何时注册(哪怕在微任务里调用 setTimeout),都只能进宏任务队列尾部,要等当前宏任务 + 所有微任务 + 渲染(浏览器)之后,才可能被取出。而微任务没有这种“排队等待”,只要诞生,就注定是本轮的“最后一批执行者”。
- 典型对比:
Promise.resolve().then(() => setTimeout(() => console.log('macro'), 0));中的setTimeout回调属于下一轮宏任务,不会打断当前微任务流 - 但同一行里的
Promise.resolve().then(() => Promise.resolve().then(() => console.log('micro'))),第二个then就是本轮微任务
实际开发中要注意的点
这种“立即追加、本轮执行”的机制,让微任务非常高效,但也容易引发意料之外的执行顺序或性能问题。
- 避免在微任务中无节制地生成新微任务,防止“微任务风暴”导致页面卡顿
- 不要假设
Promise.then一定“稍后执行”——它可能比紧随其后的setTimeout快得多,甚至快几个数量级 - 调试时可借助
queueMicrotask显式插入微任务,比依赖Promise更轻量、语义更清晰











