You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Microtask Queue优先级高于Macrotask Queue吗?为何fetch响应晚于setTimeout输出

事件循环执行顺序问题解答

参考代码

console.log("scriptStart")

setTimeout(() => {
  console.log("setTimeout");
}, 0);

fetch("https://jsonplaceholder.typicode.com/todos/1").then(() => {
  console.log('Response');
});

for (let i = 0; i < 100000000; i++) {
  const start = Date.now();
}
console.log("scriptEnd")

核心原理误区澄清

很多人会误以为fetch响应返回后then回调会立刻进入微任务队列,这是错误的:fetch的网络请求完成后,浏览器会先将「fetch响应解析处理」作为一个独立宏任务推入宏任务队列,只有这个宏任务执行时,才会将对应的then回调推入微任务队列。
微任务优先级更高的规则仅作用于「同一个宏任务执行完成后的微任务清空阶段」,不会跳过队列中排在前面的宏任务,直接执行后续宏任务产生的微任务。

完整执行流程梳理

我们按代码执行顺序一步步拆解:

  1. 主线程启动,执行当前script脚本(这本身是事件循环的第一个宏任务),首先执行console.log("scriptStart"),输出scriptStart。
  2. 执行setTimeout,将定时任务交给浏览器定时器线程处理,由于设置延迟为0,且后续for循环阻塞时间远大于浏览器规定的setTimeout最小延迟(通常为4ms),定时器到期后立刻将setTimeout回调推入宏任务队列,此时宏任务队列内容:[setTimeout回调]。
  3. 执行fetch,将网络请求交给浏览器网络线程处理,题目明确说明for循环阻塞时间足够长到fetch响应返回,因此网络线程拿到响应后,浏览器会将「fetch响应处理任务」推入宏任务队列,此时宏任务队列内容:[setTimeout回调, fetch响应处理任务]。
  4. 执行耗时很长的for循环,阻塞主线程,期间所有异步任务的回调都不会被执行,只能在对应队列中等待。
  5. for循环执行结束,执行console.log("scriptEnd"),输出scriptEnd,第一个宏任务(当前script脚本)执行完成。
  6. 按照事件循环规则,当前宏任务执行完成后会清空所有微任务:此时微任务队列为空(fetch的then回调还没有被推入微任务队列,因为对应的fetch响应处理宏任务还没执行),因此没有微任务需要执行。
  7. 事件循环从宏任务队列取队首的任务执行,也就是先执行setTimeout回调,输出setTimeout。
  8. setTimeout回调执行完成后,再次清空微任务队列,此时队列依然为空,无额外输出。
  9. 事件循环再次从宏任务队列取队首的任务,也就是fetch响应处理任务,执行该任务解析响应内容,完成后将then回调推入微任务队列。
  10. fetch响应处理任务执行完成,清空微任务队列,执行then回调,输出Response。

最终输出顺序

和我们推导的一致,最终输出顺序为:

  • scriptStart
  • scriptEnd
  • setTimeout
  • Response

内容的提问来源于stack exchange,提问作者glafche

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.29 09:39:01