JavaScript异步与同步按事件循环机制交织运行:同步代码优先执行并阻塞后续,异步任务分微任务(如Promise.then)和宏任务(如setTimeout),每次同步结束后先清空微任务队列再执行一个宏任务。

JavaScript 中异步函数和同步代码不是“互斥开关”,而是按事件循环机制自然交织运行的。关键不在“谁先谁后”,而在于任务如何被调度、何时被推入执行栈。
同步代码总是优先跑完
所有顶层语句、函数体内直接执行的语句(如变量赋值、console.log、for循环、普通函数调用)都属于同步任务,它们会连续压入调用栈并立即执行,直到栈空。
哪怕里面嵌套了耗时操作(比如一个 3 秒的 while 循环),它也会阻塞后续一切——包括已经注册但尚未触发的异步回调。
- 同步任务执行期间,宏任务队列和微任务队列都在“排队”,不会插队
- 浏览器 UI 渲染也必须等当前同步段彻底结束才会进行
- 这就是为什么长同步计算会让页面卡死:JS 引擎没空去处理任何其他事
异步任务靠“提交+排队+执行”三步走
像 setTimeout、fetch、Promise 这类异步操作,本质是 JS 引擎把任务“交出去”,再约定好“什么时候回来”。整个过程分四阶段:
立即学习“Java免费学习笔记(深入)”;
- 提交:JS 引擎当场完成(例如调用 setTimeout 那一刻就注册好计时器)
- 后台执行:由浏览器或 Node.js 的线程池/系统 API 独立完成(JS 引擎不参与)
- 排队:完成后,回调被放入任务队列(微任务队列优先级高于宏任务)
- 执行:等当前所有同步代码跑完,事件循环才从队列取任务进栈
所以你看到 console.log('1') → setTimeout(..., 0) → console.log('2'),输出一定是 1、2、然后才是 setTimeout 回调——哪怕延时设为 0,它也得等同步段收尾。
微任务与宏任务决定“穿插节奏”
异步回调不是统一排队的。JS 把它们分两类,执行顺序严格固定:
- 微任务:Promise.then/catch/finally、queueMicrotask、async 函数内部 await 后的代码
- 宏任务:setTimeout、setInterval、I/O、用户事件(click)、new Promise 构造函数里的代码(注意:构造函数体是同步的)
执行规则就一条:每次同步代码结束后,先清空全部微任务队列,再取一个宏任务执行。
这意味着,一个 Promise 链可以“插在”两个 setTimeout 之间多次执行,形成细粒度交织;而多个 setTimeout 则严格按注册顺序一个接一个来。
交织不是随意混搭,而是可预测的调度结果
所谓“交替穿插”,其实是事件循环按固定节奏推进的表现:同步 → 微任务 → 宏任务 → 同步 → 微任务 → …… 每次只取一个宏任务,但微任务可以批量清空。
这种机制让开发者既能避免阻塞(靠异步移交耗时工作),又能控制响应顺序(靠微任务抢占时机)。比如用 Promise.resolve().then() 把一段逻辑“挪到本轮末尾执行”,比 setTimeout(fn, 0) 更快、更确定。
真正需要警惕的,是误以为 await 或 .then 会“中断当前函数并跳转”,其实它们只是把后续逻辑包装成微任务排队——函数本身早已返回,上下文早已出栈。


















