javascript事件循环执行顺序的核心是:同步代码执行完后立即清空微任务队列,再执行一个宏任务,且每轮宏任务后必清空微任务队列;promise.then属微任务,settimeout属宏任务,故前者总先于后者执行。

JavaScript 事件循环中分析复杂异步代码的执行顺序,核心是理清调用栈、宏任务队列、微任务队列三者的协作关系,并准确识别每段代码属于哪一类任务。只要分清宏任务(如 setTimeout、setInterval、I/O、UI渲染)和微任务(如 Promise.then/catch/finally、queueMicrotask、MutationObserver),就能逐步推演执行流。
一、牢记事件循环的基本步骤
每次循环(一个 tick)按以下固定顺序执行:
- 执行当前调用栈中的同步代码(直到为空)
- 检查并清空微任务队列:逐个执行所有已入队的微任务(期间新加入的微任务也会在本轮执行完)
- 执行一个宏任务(如 setTimeout 回调、I/O 回调等)
- 再次清空微任务队列(宏任务执行后立即执行)
- 重复上述流程
二、快速识别任务类型是关键前提
很多执行顺序错误,源于误判某段代码的归属。常见归类参考:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
-
宏任务:
setTimeout/setInterval、setImmediate(Node.js)、I/O 回调、requestAnimationFrame(浏览器)、用户交互事件(click 等) -
微任务:
Promise的回调(.then/.catch/.finally)、queueMicrotask(fn)、MutationObserver回调 -
注意陷阱:
-
new Promise(fn)中的fn是同步执行的(不是微任务) -
async function内部同步代码是宏任务的一部分;await后的代码会被包装成微任务(等价于.then) -
process.nextTick(Node.js)优先级高于微任务,但仅限 Node 环境
-
三、分步拆解法:把代码“切片”进事件循环阶段
面对嵌套异步(如 Promise 链 + setTimeout + async/await),建议按时间轴手动划分阶段:
- 第 1 步:标出所有同步代码块(包括 new Promise 构造函数内、async 函数开头到第一个 await 前)
- 第 2 步:标记所有微任务入口(.then/.catch/await 后续代码/queueMicrotask)
- 第 3 步:标记所有宏任务入口(setTimeout/setInterval 等)
-
第 4 步:从头开始模拟:
- 执行同步部分 → 遇到 Promise 构造器就立即执行 executor → 遇到 .then 就入微任务队列
- 同步结束 → 执行全部微任务(注意:新产生的微任务也在此轮执行)
- 取一个宏任务(通常是最早入队的那个)→ 执行其回调 → 执行该回调产生的所有微任务
- 继续下一轮
例如:console.log(1);<br>setTimeout(() => console.log(2), 0);<br>Promise.resolve().then(() => console.log(3));<br>console.log(4);
执行顺序为:1 → 4 → 3 → 2(同步 1 和 4;微任务 3;宏任务 2)
四、调试辅助技巧
光靠推理易出错,结合工具可验证判断:
- 在关键位置加
console.log('step X'),配合时间戳或序号观察实际输出顺序 - 使用
debugger断点,配合浏览器 DevTools 的 Sources 面板查看调用栈和任务队列状态 - Chrome DevTools 中开启 Rendering > Paint Flashing 或 Layers 可辅助观察宏任务间 UI 渲染时机(渲染是宏任务间隙发生的)
- Node.js 中可用
process.nextTick和Promise.resolve().then()对比,体会微任务内部优先级差异
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










