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回调推入微任务队列。
微任务优先级更高的规则仅作用于「同一个宏任务执行完成后的微任务清空阶段」,不会跳过队列中排在前面的宏任务,直接执行后续宏任务产生的微任务。
完整执行流程梳理
我们按代码执行顺序一步步拆解:
- 主线程启动,执行当前script脚本(这本身是事件循环的第一个宏任务),首先执行
console.log("scriptStart"),输出scriptStart。 - 执行
setTimeout,将定时任务交给浏览器定时器线程处理,由于设置延迟为0,且后续for循环阻塞时间远大于浏览器规定的setTimeout最小延迟(通常为4ms),定时器到期后立刻将setTimeout回调推入宏任务队列,此时宏任务队列内容:[setTimeout回调]。 - 执行
fetch,将网络请求交给浏览器网络线程处理,题目明确说明for循环阻塞时间足够长到fetch响应返回,因此网络线程拿到响应后,浏览器会将「fetch响应处理任务」推入宏任务队列,此时宏任务队列内容:[setTimeout回调, fetch响应处理任务]。 - 执行耗时很长的for循环,阻塞主线程,期间所有异步任务的回调都不会被执行,只能在对应队列中等待。
- for循环执行结束,执行
console.log("scriptEnd"),输出scriptEnd,第一个宏任务(当前script脚本)执行完成。 - 按照事件循环规则,当前宏任务执行完成后会清空所有微任务:此时微任务队列为空(fetch的
then回调还没有被推入微任务队列,因为对应的fetch响应处理宏任务还没执行),因此没有微任务需要执行。 - 事件循环从宏任务队列取队首的任务执行,也就是先执行
setTimeout回调,输出setTimeout。 - setTimeout回调执行完成后,再次清空微任务队列,此时队列依然为空,无额外输出。
- 事件循环再次从宏任务队列取队首的任务,也就是fetch响应处理任务,执行该任务解析响应内容,完成后将
then回调推入微任务队列。 - fetch响应处理任务执行完成,清空微任务队列,执行
then回调,输出Response。
最终输出顺序
和我们推导的一致,最终输出顺序为:
scriptStartscriptEndsetTimeoutResponse
内容的提问来源于stack exchange,提问作者glafche
相关产品推荐
相关产品推荐

