微任务队列每次宏任务结束后必须立即、连续、无间断清空,若新微任务执行中又生成微任务,将导致无限循环,使主线程卡死,无法响应用户操作、渲染界面或执行宏任务。

因为微任务队列会在**每次宏任务结束后立即、连续、无间断地清空**,一旦新微任务在执行中又生成新的微任务,就会形成“产-消-再产-再消”的无限闭环,主线程永远无法进入下一个宏任务,也就无法响应用户操作、更新界面或执行定时器。
微任务的执行机制本身就不允许“喘口气”
JavaScript 引擎规定:当前宏任务(比如一段 script 脚本、一个 setTimeout 回调)执行完毕后,必须立刻检查微任务队列;如果非空,就持续取出第一个任务执行,执行过程中产生的任何新微任务(如 Promise.then、queueMicrotask)会追加到队列末尾,引擎接着执行下一个——直到队列彻底为空,才停止。
这个过程是同步、阻塞、不可中断的。它不看时间、不查优先级、不等渲染、不响应点击,只认“队列是否空”。所以只要队列始终不空,主线程就一直卡在微任务里。
和宏任务的关键区别:调度粒度不同
宏任务是“一次只取一个”,执行完就得让出控制权,给渲染、用户事件、下一个 setTimeout 留出机会;而微任务是“一次清空全部”,没有数量限制,也没有执行时长限制。
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
立即学习“Java免费学习笔记(深入)”;
常见误区是以为“Promise.then 是异步,所以不会卡”——其实它只是不卡在当前同步代码里,但一旦被触发,就可能开启一场微任务雪崩:
- ❌ 错误写法:在 Promise.then 里无条件再次调用自身或 queueMicrotask
- ✅ 正确做法:加入计数器、处理完成标志、数据长度判断等明确退出条件
实际影响不只是“看起来卡”,而是全面失联
主线程卡死在微任务循环中时,以下所有功能都会停滞:
- 页面滚动、按钮点击、键盘输入完全无响应
- setTimeout/setInterval 不会触发(它们是宏任务,轮不到)
- DOM 更新不会渲染(UI 渲染发生在微任务清空之后)
- fetch 请求虽然已发出,但其 .then 回调也排不进队列(微任务队列已满)
本质是单线程模型下的逻辑死锁
JavaScript 的单线程设计决定了它没有“抢占式调度”。微任务链式自调用相当于自己给自己不断发号,却不交还叫号机。事件循环只能按规则执行,结果就是整个调度系统停摆。这不是浏览器 bug,而是机制严格执行带来的必然后果。

















