任务队列分为宏任务队列和微任务队列两类;宏任务由宿主环境发起,如script执行、setTimeout、I/O回调等,每次事件循环执行一个;微任务由JS引擎在宏任务结束后立即连续执行全部,如Promise回调、queueMicrotask等。

任务队列不是单一队列,而是两类队列的统称:宏任务队列和微任务队列。所谓“任务队列”在规范中其实并不存在一个叫 Task Queue 的总容器;它只是对异步回调存放位置的泛称。真正区分的关键,在于任务类型——是宏任务,还是微任务。
宏任务有哪些典型来源
宏任务由宿主环境(浏览器或 Node.js)发起,代表一次较完整的执行单元。常见包括:
- 整个 script 标签或 JS 文件的同步执行(第一个宏任务)
- setTimeout、setInterval 的回调
- setImmediate(仅 Node.js)
- I/O 操作完成后的回调(如 fetch、XMLHttpRequest、fs.readFile)
- UI 渲染(浏览器中的一帧绘制)、用户事件(click、input 等)
- postMessage、MessageChannel 的消息处理
微任务不是“小号宏任务”,而是独立调度机制
微任务不由宿主直接调度,而是由 JavaScript 引擎在特定时机主动触发。它们不进宏任务队列,而进入专属的微任务队列。典型来源有:
- Promise.then/catch/finally 回调
- MutationObserver 的回调(监听 DOM 变化)
- queueMicrotask() 显式加入的微任务
- process.nextTick()(仅 Node.js)
执行时机的根本区别
每次事件循环只从宏任务队列中取出一个宏任务执行;而一旦该宏任务结束,引擎会立即、连续、全部执行完当前微任务队列中的所有任务,中间不穿插任何渲染或新宏任务。
也就是说:
- 宏任务之间必然隔一次事件循环,中间可能伴随 UI 渲染
- 微任务总在“上一个宏任务刚结束、下一个宏任务还没开始”的间隙执行
- 哪怕在微任务里又创建了新的微任务(比如 Promise.then 里再 new Promise),也会被追加到当前微任务队列末尾,继续执行,直到队列清空
一个直观例子帮你记住顺序
这段代码的输出顺序是:start → end → promise → setTimeout
console.log('start');
setTimeout(() => console.log('setTimeout'), 0);
Promise.resolve().then(() => console.log('promise'));
console.log('end');
原因:前两行和最后一行是同步代码(属于第一个宏任务),先输出 start 和 end;接着清空微任务队列,执行 promise;最后才轮到宏任务队列里的 setTimeout 回调。


















