微任务中再添加微任务会持续追加到当前队列尾部并连续执行直至清空,该过程不可中断、不插队宏任务、不触发渲染;其“递归清空”指队列行为延续而非函数调用栈递归,引擎无深度限制,但失控将导致宏任务无限推迟和ui渲染阻塞。

微任务中再添加微任务,会持续追加到当前微任务队列尾部,并被事件循环连续执行,直到队列彻底清空——这个过程不中断、不插队宏任务,也不触发渲染。
微任务会“递归清空”,不是“递归调用”
这里的“递归”是队列行为上的延续,而非函数调用栈意义上的递归。每次新微任务(如 Promise.then、queueMicrotask)被加入,都排在当前微任务队列末尾;事件循环会持续取出并执行,直到一个都不剩。
- 例如:Promise.resolve().then(() => { console.log(1); Promise.resolve().then(() => console.log(2)); }); 输出一定是 1 → 2,中间不会穿插任何宏任务或渲染
- 微任务队列是单队列、FIFO 结构,新增任务只影响执行顺序长度,不改变执行时机
- 浏览器或 Node.js 引擎没有“深度限制”,只要队列非空,就继续执行
后果一:宏任务被无限推迟
只要微任务队列没清空,事件循环就不会取下一个宏任务(比如 setTimeout 回调、用户点击事件、fetch 响应处理),更不会进入渲染阶段。
- 页面可能完全卡死:按钮点不动、滚动冻结、动画停摆
- 开发者工具里能看到主线程长时间占用,但无报错
- 不同于同步死循环(会直接报栈溢出),这种卡顿更隐蔽,容易误判为性能问题
后果二:UI 渲染被阻塞
浏览器通常在每轮宏任务结束后、下一轮宏任务开始前尝试渲染。而微任务全部执行期间,渲染被跳过。
- 即使 DOM 已更新,用户也看不到变化,直到所有微任务跑完
- Vue/React 的异步更新依赖微任务批量合并,但如果微任务链失控,反而导致视图延迟响应
- 长时间运行的微任务(如万级数据遍历+Promise.then)会让页面“假死”数秒
如何安全使用嵌套微任务
不是不能嵌套,而是要有明确边界和退路。
- 加终止条件:比如限制最大递归深度,或用计数器控制微任务生成次数
- 重负载拆分:把耗时操作移到 setTimeout 或 requestIdleCallback 中,主动让出主线程
- 优先用 queueMicrotask 而非 Promise.then 做轻量调度,避免不必要的 Promise 构造开销
- 监控微任务长度:开发期可用 performance.mark + performance.measure 粗略追踪微任务堆积情况











