setTimeout与fetch API执行顺序疑问:实际输出为何与预期不符?
为什么fetch的回调比无延迟setTimeout晚执行?
先拆解你的代码执行的完整流程,就能明白顺序为什么是这样:
1. 同步代码阶段
首先JS引擎会先执行所有同步代码:
- 调用
fetch发起网络请求,这个请求是浏览器层面的异步操作,需要等待服务器返回响应,此时then里的回调还不会进入微任务队列。 - 注册第一个
setTimeout(..., 1000):把回调放到浏览器的延迟队列,1秒后才会转移到宏任务队列等待执行。 - 注册第二个无延迟的
setTimeout:浏览器会在同步代码执行完毕后,立刻把这个回调放到宏任务队列的队首。 - 执行
console.log("HI this is test"),输出这句话。
2. 同步代码执行完毕后的事件循环
事件循环的规则是:先清空所有微任务队列,再执行宏任务队列里的任务,但这里有个关键:
此时fetch的网络请求还没完成(哪怕只需要几十毫秒),所以微任务队列是空的。
于是引擎开始执行宏任务队列里的第一个任务:无延迟的setTimeout回调,输出Callback with no delay。
3. 宏任务执行后的微任务检查
当这个宏任务执行完,引擎再次检查微任务队列——这时候fetch的请求已经拿到了响应,fetch返回的Promise状态变为resolved,对应的then回调被加入微任务队列:
- 第一个
then把响应转成json(这又返回一个Promise),第二个then的回调接着进入微任务队列。 - 引擎依次执行这些微任务,最终输出
{userId: 1, id: 1, title: 'delectus aut autem', completed: false}。
4. 延迟的setTimeout执行
等待1秒后,第一个setTimeout的回调被移到宏任务队列,引擎执行它,输出Callback with delay。
你之前认知的误区
你以为fetch的Promise回调会立刻进入微任务队列,但实际上:
- 普通的
Promise.resolve()会立刻把回调加入微任务队列,但fetch的Promise是依赖网络请求的异步结果的,只有当浏览器拿到服务器响应后,才会把then回调加入微任务队列。 - 无延迟的
setTimeout不需要等待网络,同步代码一结束就进入宏任务队列,自然会先于还没完成的fetch回调执行。
如果把fetch换成一个立刻resolve的Promise,比如:
Promise.resolve('test').then(res => console.log(res)) setTimeout(()=>{ console.log("Callback with no delay") }) console.log("HI this is test")
输出顺序就会是:
HI this is test test Callback with no delay
这才符合你最初对微任务优先的认知。
内容的提问来源于stack exchange,提问作者Cool Coder
相关产品推荐
相关产品推荐

