破解js异步时序的关键是掌握「同步→微任务→宏任务」三步闭环:同步代码最先执行且一气呵成;微任务在同步后清空整个队列,含promise.then、async/await等;宏任务每次只取一个,且必待微任务清空后才执行,含settimeout、事件回调等。

要破解 JS 异步时序迷局,关键不是背顺序,而是掌握「同步 → 微任务 → 宏任务」这个不可跳过的三步闭环,并理解每一步的触发条件和清空规则。
同步代码永远最先执行,且一气呵成
所有直接写在 script 中、未被包装进异步 API 的语句,都属于同步任务。它们按书写顺序压入调用栈,逐行执行,中途不插队、不暂停。
- 比如 console.log('a')、let x = 1 + 1、for 循环 都是同步的
- 遇到 setTimeout 或 Promise.resolve().then,JS 不会等结果,而是立刻登记回调,继续往下跑同步代码
- 哪怕 setTimeout 延时设为 0,它的回调也绝不会在当前同步块内执行
微任务总在同步结束后「一口气清完」
微任务不是“执行一个”,而是“清空整个队列”。只要队列里还有任务,哪怕执行中又新增了微任务,也会继续执行,直到彻底为空。
- 典型微任务:Promise.then/catch/finally、async/await 中 await 后的代码、queueMicrotask()、MutationObserver
- 注意:process.nextTick(Node.js)优先级比 Promise 还高,但浏览器环境不支持
- 常见陷阱:在 then 回调里再 new 一个 Promise 并调用 then,新微任务会加入当前队列尾部,本轮仍会执行
宏任务每次只取一个,且必须等微任务清完才轮到
宏任务之间天然隔开,每个宏任务执行完,都会强制先处理完所有待命的微任务,再取下一个宏任务——这是时序稳定的核心保障。
- 典型宏任务:setTimeout/setInterval、script 整体代码(第一个宏任务)、DOM 事件回调(如 click)、fetch/ajax 回调、requestAnimationFrame(渲染前特殊宏任务)
- 注意:UI 渲染(重绘/回流)通常发生在宏任务之间、微任务之后,这也是为什么多次修改 DOM 后用 requestAnimationFrame 能拿到最新布局
- 容易忽略:页面加载时,每个 <script> 标签本身就是一个独立宏任务</script>,跨 script 的 setTimeout 和 Promise 时序会受此影响
实战判断口诀:看谁「发起」、看谁「调度」
区分宏微任务,不靠死记,而看两个本质:
- 谁发起? —— JS 引擎自己生成的任务(如 Promise 状态变更触发的 then)→ 多为微任务
- 谁调度? —— 浏览器或 Node 等宿主环境通过系统级机制(定时器、I/O 完成、用户输入)通知 JS 执行 → 多为宏任务
- 简单验证法:在微任务中写 setTimeout(() => console.log('macro'), 0),它一定排在当前所有微任务之后;而在宏任务中写 Promise.resolve().then(...),该微任务会紧接在本宏任务末尾执行,早于下一个宏任务











