JavaScript异步靠单线程+事件循环实现;宏任务(如setTimeout)每轮只执行一个,之后清空全部微任务(如Promise.then、queueMicrotask);微任务在本轮宏任务结束后立即、一次性执行完。

JavaScript 的异步不是靠多线程实现的,它靠的是单线程 + 事件循环(Event Loop)——这个机制决定了 setTimeout、Promise、用户点击、网络响应等何时真正执行。
宏任务和微任务怎么分?顺序怎么排?
事件循环每次只处理一个宏任务(macrotask),比如 script 全局代码、setTimeout 回调、setInterval 回调、I/O 回调;而每个宏任务执行完后,会清空全部当前轮次的微任务(microtask),比如 Promise.then、MutationObserver 回调、queueMicrotask。
-
setTimeout(() => console.log('timeout'), 0)是宏任务,下一轮才执行 -
Promise.resolve().then(() => console.log('promise'))是微任务,本轮宏任务结束后立刻执行 - 多个
Promise.then会按注册顺序依次执行,不会被中间插入的setTimeout打断 -
queueMicrotask和Promise.then属于同一级微任务队列,但前者更“原始”,无异常捕获逻辑
为什么 setTimeout(fn, 0) 不是立刻执行?
因为 setTimeout 的回调会被放入宏任务队列,必须等当前所有同步代码 + 当前轮次全部微任务执行完,才能轮到它。即使设为 0,也只是“尽快安排”,不等于“马上运行”。
- 浏览器对最小延迟有强制限制(通常 ≥4ms),在非活跃标签页中可能升至 1000ms
- Node.js 中
setTimeout(fn, 0)实际等价于setImmediate(fn)(在 I/O 队列之后、下次事件循环之前) - 想比
setTimeout更快触发异步逻辑,用queueMicrotask(fn)或Promise.resolve().then(fn)
async/await 底层怎么跟事件循环配合?
async 函数本身是同步执行的,遇到 await 后,如果后面是个非 Promise 值,会立即包装成已 resolve 的 Promise,然后暂停函数,并把后续代码变成微任务推入队列。
立即学习“Java免费学习笔记(深入)”;
-
await 123等价于await Promise.resolve(123),后续代码进入微任务 -
await fetch('/api')会等待网络完成,完成后将.then回调作为微任务加入队列 - 连续多个
await不会“阻塞事件循环”,只是把每个 await 后的逻辑拆成独立微任务,仍受微任务清空规则约束 - 不要在循环里密集写
await sleep(10),这会累积大量微任务,可能卡住 UI 或拖慢响应
真正容易被忽略的,是微任务队列在每次宏任务结束时“一次性清空”的特性——哪怕你在某个 Promise.then 里又注册了十个新的 Promise.then,它们都会在这轮里全部执行完,不会等到下一轮宏任务才开始。这个细节直接影响错误边界、状态更新节奏和性能敏感场景的表现。











