异步处理函数执行需经注册、挂起、入队、调度、执行、清理六阶段;注册仅声明待办,挂起后按宏/微任务分队列,事件循环优先清空微任务再取宏任务,执行后即释放。

异步处理函数在事件循环中不是“一注册就执行”,而是经历明确的几个阶段:注册、挂起、入队、调度、执行、清理。关键不在“什么时候开始”,而在“它被谁管理、在哪排队、何时轮到它”。
注册阶段:只是告诉环境“我将来要干活”
像 setTimeout(fn, 0)、Promise.resolve().then(fn)、fetch() 这类调用,本质是向浏览器或 Node.js 的运行时发起一个请求:把回调函数交给对应的系统模块(如定时器模块、网络模块、Promise 状态机)去跟踪和管理。此时 fn 还没进任何队列,也不在调用栈里。
挂起与入队阶段:按类型分发到不同队列
异步操作完成后,运行时会根据任务性质,把回调放进对应的任务队列:
- 宏任务队列:setTimeout、setInterval、I/O 回调、用户事件(click)、setImmediate(Node.js)
- 微任务队列:Promise.then/catch/finally、queueMicrotask()、MutationObserver 回调
注意:两个队列彼此独立,且微任务队列优先级更高——它不等下一个宏任务,而是在当前宏任务执行完后立刻清空。
调度与执行阶段:事件循环按规则取任务
事件循环每一轮只做三件事:
- 从宏任务队列取出第一个任务,推入调用栈执行(同步部分)
- 同步代码执行完毕后,立即检查并逐个执行全部微任务(哪怕中途又加了新微任务,也会继续清空)
- 微任务队列为空后,进入下一轮,再取下一个宏任务
这就是为什么 Promise.then 总比 setTimeout 先输出,哪怕后者 delay 是 0——它们根本不在同一个队列,也不参与同一轮调度。
清理阶段:执行完即释放,不自动续期
回调函数执行完毕后,其执行上下文出栈,闭包引用若无其他持有者,会被垃圾回收。事件循环不会保留它、也不会重试它(除非你手动再调一次 setTimeout 或重新构造 Promise)。一次注册,一次入队,一次执行,干净利落。











