Chrome中fetch()与setTimeout()执行优先级不符预期的疑问
为什么0延迟的setTimeout会比fetch的then回调先执行?
这是个非常典型的事件循环误区,我刚学前端的时候也踩过类似的坑!你的核心问题在于把fetch返回的Promise和普通同步resolved的Promise当成了一回事,咱们一步步拆解来搞明白:
先明确事件循环的基础规则
正常情况下,事件循环的执行顺序是固定的:
- 先执行完所有同步代码
- 清空微任务队列里的所有任务(比如Promise的
then/catch/finally、process.nextTick等) - 执行宏任务队列里的一个任务(比如
setTimeout、setInterval、UI渲染等) - 回到步骤2,循环往复
如果是普通的立即resolved的Promise,比如下面这段代码:
setTimeout(() => console.log('Async 1'), 0); console.log('Sync 2'); Promise.resolve().then(() => console.log('Promise 3')); console.log('Sync 4');
输出顺序肯定是 Sync 2 → Sync 4 → Promise 3 → Async 1,完全符合你最初的理解。那为什么换成fetch就不一样了?
fetch的Promise特殊在哪里?
fetch发起的是网络请求,它的Promise状态变更依赖于浏览器的网络线程,而非JS主线程:
- 当你调用
fetch()时,JS主线程只是给浏览器发了个指令“帮我发个网络请求”,然后就继续执行后续同步代码了 - 网络线程需要完成DNS解析、建立连接、发送请求、等待服务器响应、接收数据这一系列操作,这个过程和JS主线程的事件循环并行,但耗时是不确定的——哪怕是请求缓存资源,也需要几毫秒的处理时间
- 只有当网络线程把完整响应拿回来后,才会通知JS主线程:“fetch请求完成了,把then里的回调放进微任务队列吧”
再看你的代码执行时序
咱们把你的代码流程拆解开:
- 执行
setTimeout(() => console.log('Async 1'), 0):浏览器启动计时器,至少等待4ms(现代浏览器对0延迟的setTimeout有最小时间限制,这是HTML标准规定的)后,把回调加入宏任务队列 - 打印
Sync 2 - 执行
fetch(...):浏览器启动网络线程处理请求,此时Promise处于pending状态,then回调还没被加入任何队列 - 打印
Sync 4 - 同步代码执行完毕,检查微任务队列:空的(因为fetch请求还没完成,Promise还没resolved)
- 事件循环等待宏任务:过了4ms后,
setTimeout的回调被加入宏任务队列,事件循环立刻执行它,打印Async 1 - 等到网络线程完成请求,把fetch的Promise改为resolved,
then回调被加入微任务队列 - 下一次事件循环清空微任务队列,打印
Promise 3
所以不是“未resolved的Promise优先级低于宏任务”,而是fetch的Promise还没来得及把回调放进微任务队列,setTimeout的回调就已经被加入宏任务队列并执行了。
验证一下
你可以把fetch换成一个立即resolved的Promise,看看输出顺序是不是符合你最初的预期:
setTimeout(() => console.log('Async 1'), 0); console.log('Sync 2'); // 用立即resolved的Promise替代fetch Promise.resolve().then(() => console.log('Promise 3')); console.log('Sync 4');
这时候输出顺序就是 Sync 2 → Sync 4 → Promise 3 → Async 1,完全符合事件循环的基础规则。
内容的提问来源于stack exchange,提问作者Johnny Huynh
相关产品推荐
相关产品推荐

