JavaScript 输出顺序取决于宏任务与微任务的调度规则:每次事件循环从一个宏任务开始,执行完后立即清空所有微任务队列,再进入下一宏任务;微任务可递归添加但仍属本轮执行。

分析 JavaScript 中复杂输出顺序的关键,在于理清宏任务(macro-task)和微任务(micro-task)的执行层级,以及它们在每次 Event Loop 循环中的调度规则。不是代码写在前面就先执行,而是取决于任务类型、调用时机和嵌套关系。
宏任务与微任务的执行顺序必须分清
每次 Event Loop 从一个宏任务开始(如 script、setTimeout、setInterval、I/O、UI 渲染),执行完该宏任务后,会立即、连续、清空当前所有微任务队列(如 Promise.then、MutationObserver、queueMicrotask),之后才进入下一个宏任务。
- Promise 构造函数内的代码是同步执行的,但 .then/.catch 回调属于微任务
- async/await 函数内部 await 后面的代码会被包装成微任务(等价于 Promise.then)
- setTimeout 和 setInterval 的回调是宏任务;setImmediate(Node.js)和 requestIdleCallback 是宏任务;process.nextTick(Node.js)是微任务(优先级高于 Promise)
注意嵌套和动态添加任务的影响
微任务可以创建新的微任务(比如在 Promise.then 里再 new Promise().then),这些新微任务会加入本轮微任务队列末尾,仍会在当前宏任务结束前执行完——不会等到下一轮。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 例如:Promise.resolve().then(() => { console.log(1); Promise.resolve().then(() => console.log(2)); }); 会输出 1、2(都在同一轮微任务中)
- 而 setTimeout(() => { Promise.resolve().then(() => console.log(3)); }, 0); 中的 console.log(3) 要等到下一轮宏任务之后的微任务阶段才执行
浏览器环境要留意渲染时机
宏任务之间可能触发 UI 渲染(如用户交互、定时器到期、网络响应),但微任务总是在渲染前执行。这意味着:即使你快速连续修改 DOM,只要都在同一个宏任务内,用户只看到最终结果;而如果中间插入了微任务,它可能读取到未渲染的中间状态。
立即学习“Java免费学习笔记(深入)”;
- document.body.innerHTML = 'A'; Promise.resolve().then(() => console.log(document.body.innerHTML)); // 输出 'A',此时还没渲染
- 但 requestAnimationFrame 回调会在下一次重绘前执行,属于宏任务,介于微任务和渲染之间
调试复杂顺序的实用方法
不要靠直觉猜,要用可验证的方式拆解:
- 给每个输出加明确标识(如 console.log('setTimeout-1')),避免混淆
- 用 queueMicrotask() 显式插入微任务,比依赖 Promise.then 更直观
- 在关键位置打 debugger 或使用 DevTools 的“Event Loop”面板(Chrome Canary 支持)观察任务排队情况
- 把代码按「宏任务入口 → 同步代码 → 微任务注册 → 下一宏任务触发」逐层标注,画简易时序图

















