异步任务源决定回调进入宏任务队列或微任务队列:宏任务源包括settimeout、setinterval、用户交互事件等;微任务源包括promise.then/.catch/.finally、mutationobserver、queuemicrotask()。

异步任务源(Task Sources)是浏览器用来分类和调度异步回调的底层机制。它不是开发者直接调用的 API,而是规范中定义的一组“来源类别”,每个来源对应一类异步行为,并决定其回调被放入宏任务队列还是微任务队列。
常见的异步任务源及其归属
浏览器根据任务源的类型,自动将其产生的回调归入宏任务或微任务队列:
-
宏任务源(Macrotask Sources):包括
setTimeout、setInterval、requestAnimationFrame、用户交互事件(如click、input)、页面加载事件(load、DOMContentLoaded),以及整个<script></script>标签的执行本身也属于宏任务源。 -
微任务源(Microtask Sources):包括
Promise的.then/.catch/.finally回调、MutationObserver的回调、queueMicrotask()显式提交的任务。在 Node.js 中还有process.nextTick,但它不属于浏览器环境。
浏览器如何区分任务源
区分依据不是回调函数写法,而是触发该回调的原始操作所属的规范定义。例如:
- 调用
setTimeout(fn, 0)时,浏览器按 HTML 规范将fn标记为“timer task source”,归入宏任务队列; - 调用
Promise.resolve().then(fn)时,引擎按 ECMAScript 规范将fn标记为“promise job source”,归入微任务队列; - 哪怕
setTimeout和Promise.then都写成() => console.log('x'),浏览器仍通过调用栈上游的 API 类型判断任务源,而非函数内容。
为什么区分任务源很重要
任务源决定了执行优先级和时机:
- 每次宏任务执行完毕后,浏览器会清空整个微任务队列**(不是只执行一个)**,再进行 UI 渲染,然后取下一个宏任务;
- 这意味着由微任务源产生的回调,总比同一批宏任务源的回调更早执行——比如
Promise.then一定在setTimeout之前,哪怕两者都设为 0 毫秒; - 不同任务源之间不会混排:宏任务队列和微任务队列是两个独立结构,浏览器不会把 Promise 回调插进 setTimeout 队列里。
实际验证小技巧
可以通过嵌套观察执行顺序来反推任务源归属:
- 同步代码 → 所有当前微任务(如 Promise 回调)→ 渲染 → 下一个宏任务(如 setTimeout);
- 在
setTimeout回调里新建Promise,它的.then会进入**下一轮**微任务队列,而不是插在当前宏任务末尾; - 用
Performance.now()或 Chrome DevTools 的 Performance 面板,能看到“Promise.then”出现在“Timer Fired”之后、但紧挨着“Rendering”之前。











