事件循环是javascript单线程下实现异步非阻塞的核心机制,通过宏任务与微任务队列分级调度,确保任务交替、有序、不阻塞地执行;微任务(如promise.then)在当前宏任务结束后立即全部清空,宏任务(如settimeout)每次仅执行一个,并支持分片以避免主线程长时间占用。

事件循环不是让多个异步任务“同时跑”,而是让它们在单线程中**交替、有序、不阻塞地推进**。关键不在“多”,而在“协同”——靠队列分级、执行时机控制和任务粒度管理来实现。
微任务优先,保证响应及时性
Promise.then、queueMicrotask、MutationObserver 这类微任务,会在当前宏任务结束后、下一个宏任务开始前全部清空。这意味着:即使你发起 10 个 fetch 请求,只要它们的 .then 都注册了,这些回调会集中、连续地执行,中间不会被 setTimeout 或用户点击打断。
- 适合做状态聚合、错误统一处理、DOM 批量更新等需要强顺序和低延迟的操作
- 避免在微任务里做耗时计算(如大数组遍历),否则会卡住后续所有宏任务(包括页面渲染和用户交互)
宏任务分片,防止主线程长时间占用
setTimeout、setInterval、I/O 回调、UI 渲染等属于宏任务,每次事件循环只取一个执行。这个机制天然支持“任务分片”:
- 把一个长耗时操作(比如处理 10 万条数据)拆成多个小块,每块用 setTimeout 包裹,让出控制权给其他任务
- 在浏览器中,每轮宏任务后会尝试进行 UI 渲染,所以合理使用 setTimeout 可以避免界面冻结
- k6 和 Node.js 中,过长的宏任务会拖慢整个 VU(虚拟用户)或事件循环吞吐,影响压测结果真实性
协程/async 函数依赖事件循环调度
async 函数内部的 await 并不创建新线程,而是将函数挂起,并把后续代码包装成微任务(Promise.then)。它本质是语法糖,背后仍是事件循环驱动:
- await 后面的 Promise resolve 后,回调进微任务队列,等当前同步代码和已有微任务跑完才继续
- 多个 await 串行执行时,实际是“宏任务 → 微任务 → 宏任务 → 微任务”的嵌套节奏
- 想真正并发?得用 Promise.all,它把多个异步操作同时触发,由底层 I/O 多路复用并行处理,事件循环只负责收尾调度
跨环境协同要注意队列差异
浏览器、Node.js、k6、Python asyncio 的事件循环结构相似,但阶段划分和优先级细节不同:
- Node.js 有明确的 six-phase 循环(timers → pending → poll → check → close),而浏览器没有 check 和 pending 阶段
- k6 的 eventloop.go 实现更轻量,不模拟完整 Node.js 阶段,专注 HTTP 和定时器调度
- Python asyncio 的 loop.run_until_complete() 本质也是等待所有协程完成,但调度开销比 JS 更明显,小协程要合并
- 写跨平台逻辑时,别依赖 setImmediate 或 process.nextTick,它们在浏览器不可用











