宏任务是事件循环中每次执行一个、优先级低于微任务的异步任务,由宿主环境发起,包括定时器回调、UI渲染、用户交互事件、网络请求完成回调、postMessage、requestAnimationFrame等,Node.js中还有setImmediate和I/O回调。
宏任务是事件循环中每次只执行一个、优先级低于微任务的异步任务,由宿主环境(浏览器或 Node.js)发起,通常耗时较长或依赖外部系统。它们被推入宏任务队列,按先进先出顺序调度。
浏览器环境中的典型宏任务
这些操作在浏览器中明确属于宏任务,且日常开发中高频出现:
-
定时器回调:setTimeout、setInterval 的回调函数,即使延迟为 0,也必须等到当前宏任务 + 所有微任务结束后才执行。
-
UI 渲染任务:页面重绘(Repaint)与回流(Reflow),虽不可手动触发,但浏览器会在每个宏任务后、下一个宏任务前自动安排(如有 DOM 变更需更新)。
-
用户交互事件:click、input、scroll、keydown 等事件绑定的回调函数,每个事件处理本身就是一个独立宏任务。
-
网络请求完成回调:XMLHttpRequest 的 onload、fetch 的 then 链末端(注意:Promise.then 是微任务,但 fetch() 返回 Promise 的初始 resolve 是宏任务触发点;实际响应处理逻辑常包裹在微任务中,但底层 I/O 完成通知属宏任务)。
-
postMessage 与 MessageChannel 消息接收:调用 postMessage 或通过 MessageChannel.port1.postMessage 发送消息后,目标端口的 onmessage 回调进入宏任务队列。
-
requestAnimationFrame 回调:用于动画帧同步,安排在下一次浏览器渲染前执行,属于宏任务范畴。
Node.js 环境中的补充宏任务
Node.js 提供了浏览器不具备的宏任务机制:
-
setImmediate:在当前事件循环迭代结束、I/O 轮询之前执行,优先级低于 setTimeout(0),但高于下一周期的 I/O 回调。
-
I/O 回调:文件读写(fs.readFile)、数据库查询、TCP 连接建立等操作完成后的回调函数。
容易被忽略的“隐式”宏任务
有些操作看似同步,实则启动了一个宏任务生命周期:
-
<script> 标签整体执行:HTML 中每个 script 标签的内容,从加载到执行,被视为一个初始宏任务。
-
动态脚本加载完成回调:如使用 document.createElement('script') 并监听 onload,其回调属于宏任务。
-
Web Worker 的 onmessage 处理:主线程或 Worker 线程收到消息时,message 事件回调进入对应线程的宏任务队列。
判断一个操作是否为宏任务,关键看它是否由宿主环境“主动插入”任务队列,且执行时机受事件循环调度约束——不是立即执行,也不紧随同步代码之后,而是要排队等待当前宏任务和全部微任务清空后才轮到。