JavaScript fetch()执行流程存疑:实验与理论不符问题排查
解析fetch() Promise执行顺序与预期不符的原因
核心本质:浏览器事件循环的多优先级任务队列机制
浏览器的事件循环并非只有单一的宏任务队列,而是存在多类型、不同优先级的任务队列。fetch的网络响应回调属于高优先级I/O任务队列,其执行时机既不是普通微任务,也不是和setTimeout同优先级的普通宏任务。
你的实验执行流程逐阶段拆解
我们一步步梳理代码的实际执行逻辑:
当前宏任务执行阶段
- 输出
start,将第一个setTimeout回调注册到定时器宏任务队列 - 将第一个
Promise.resolve().then回调注册到微任务队列 - 输出
calling fetch,浏览器立即发起网络请求(在事件循环外并行处理) - 输出
done calling fetch,连续执行13次delay(此时网络请求已完成,但浏览器必须等待当前宏任务执行完毕才能触发后续逻辑) - 将第二个
Promise.resolve().then回调注册到微任务队列 - 将第二个
setTimeout回调注册到定时器宏任务队列 - 执行1次
delay,输出end,当前宏任务执行完毕
- 输出
微任务队列清空阶段
- 按注册顺序执行微任务队列中的两个Promise回调:输出
start promise handler→end promise handler→start 2nd promise handler→end 2nd promise handler,微任务队列彻底清空
- 按注册顺序执行微任务队列中的两个Promise回调:输出
高优先级I/O任务执行阶段
- 浏览器优先处理已完成的网络请求对应的fetch任务:resolve fetch的Promise,将其
then回调加入微任务队列 - 立即清空新生成的微任务队列:执行fetch的成功回调,输出
success start fetch handler→success end fetch handler
- 浏览器优先处理已完成的网络请求对应的fetch任务:resolve fetch的Promise,将其
普通定时器宏任务执行阶段
- 按注册顺序执行定时器宏任务队列中的两个
setTimeout回调:输出start macrotask→end macrotask→start 2nd macrotask→end 2nd macrotask
- 按注册顺序执行定时器宏任务队列中的两个
你的两个预期不成立的具体原因
预期1错误:fetch回调不会插入当前微任务队列
当网络请求完成时,当前宏任务仍在执行delay循环,浏览器无法中断正在执行的宏任务去修改微任务队列。只有等当前宏任务执行完毕、现有微任务队列清空后,浏览器才会处理fetch的resolve逻辑,此时才会将fetch的then回调加入微任务队列执行。预期2错误:fetch任务优先级高于定时器任务
浏览器对不同类型的宏任务队列设置了优先级:网络I/O完成后的回调任务优先级高于定时器任务。因此在微任务清空后,会先执行fetch的resolve任务,再执行setTimeout的回调。
内容的提问来源于stack exchange,提问作者actor_deny
相关产品推荐
相关产品推荐

