javascript事件循环执行顺序为:同步代码 → 清空当前微任务队列 → 执行一个宏任务 → 重复该循环;其中promise.then、async/await后续逻辑属微任务,settimeout属宏任务,且微任务总优先于宏任务执行。

分析复杂异步逻辑的执行顺序,核心是拆解每段代码触发的任务类型,并严格遵循事件循环的调度规则:同步代码 → 清空当前微任务队列 → 执行一个宏任务 → 再清空微任务队列 → …… 循环推进。画图不是为了追求美观,而是把“谁在哪个阶段入队、谁先被执行”可视化出来。
分三步定位每个任务的归属
遇到一段含 setTimeout、Promise、async/await、DOM 操作的代码,先逐行判断:
- 同步语句(如 console.log、变量赋值、函数调用)——立即执行,属于当前宏任务主体
- 微任务来源:Promise.then/catch/finally、queueMicrotask、MutationObserver ——它们的回调会被推入微任务队列,不等下一个宏任务,就在当前宏任务结束后立刻批量执行
- 宏任务来源:setTimeout/setInterval、I/O 回调、requestAnimationFrame、整体 script 脚本 ——它们的回调被放入宏任务队列,每次循环只取一个,且必须等前一轮所有微任务跑完才轮到它
用时间轴+双队列图呈现执行流
画图时用两条平行线表示任务队列:
- 上方标“微任务队列”,右侧标注“当前宏任务结束后立即执行,全部清空”
- 下方标“宏任务队列”,右侧标注“每次事件循环只取头一个”
- 从左到右画时间轴,标出关键节点:同步开始 → 同步结束 → 微任务执行 → 渲染(可选)→ 下一个宏任务
- 每遇到一个异步注册(如 new Promise、setTimeout),在线上对应位置画箭头指向所属队列,并写明回调内容(例如 “setTimeout cb → 宏队列”、“then(cb) → 微队列”)
实战中容易忽略的关键细节
这些点常导致预判错误:
- async 函数本身是同步的,只有 await 后面的表达式可能产生微任务(如果它返回 Promise),而 await 的后续代码会变成 then 回调,进入微任务队列
- Promise 构造器里的执行器函数是同步运行的,但 .then 是微任务;也就是说 new Promise(() => console.log('A')) 会立刻输出 A,而 .then(() => console.log('B')) 要等到同步结束后才执行
- 多个微任务嵌套不会打断当前清空过程:比如一个 then 里又调用了 queueMicrotask,新任务会追加到微任务队列尾部,继续执行直到队列为空,不会跳回宏任务
- 浏览器会插入 UI 渲染步骤:在微任务清空后、下一个宏任务开始前,浏览器可能执行一次渲染(例如 requestAnimationFrame 就卡在这个时机)
调试建议:别靠猜,用工具验证
光看代码容易误判,尤其涉及多层 Promise 或混合定时器时:
- 在关键位置加 console.log 并带上标记(如 'sync-1'、'micro-1'、'macro-1'),观察真实输出顺序
- Chrome DevTools 的 Performance 面板可以录制脚本运行,查看任务分类和执行时序
- Node.js 环境可用
process.nextTick和queueMicrotask对比优先级,辅助理解微任务层级











