JS异步执行机制疑问:主线程async函数与fetch执行顺序解析
JavaScript异步代码行为疑问
以下是测试代码:
async function longTask() { let i=0; while(true){ i++ if(i === 6000000000){ return "completed a long operation" } } } console.log("start") fetch('https://jsonplaceholder.typicode.com/todos/1') .then(response => response.json()) .then(json => console.log(json)) longTask().then(result => console.log(result)) console.log("end")
在Chrome控制台的输出为:
start // 约7-8秒后 end completed a long operation { API调用结果 }
我的疑问:
根据事件循环概念,fetch会被推入宏任务队列,返回Promise的longTask也应进入宏任务队列。我想用longTask模拟比fetch晚resolve但运行在主线程的异步代码,可从输出看,longTask似乎在调用栈执行(因console.log("end")延迟输出)。
- longTask是否未进入宏任务队列而是被推入调用栈?
- 若它像普通函数执行,"completed a long operation"应早于"end"输出;若它进入宏任务队列,fetch的回调应更早进入微任务队列,实际却是longTask的回调先执行,请解释其中原理或我的理解误区。
核心原理分析
1. async函数的执行逻辑
async函数本身是同步执行的,只有当内部遇到await或者执行到return时,才会把后续的.then回调推入微任务队列。
你的longTask函数里没有任何await,它会直接在当前调用栈中同步执行这个无限循环,直到i达到6000000000才会return。这就是为什么console.log("end")会被延迟7-8秒才输出——因为longTask()的同步执行阻塞了主线程,直到它执行完,才会继续执行后面的console.log("end")。
2. 输出顺序的深层原因
当longTask执行到return时,会把返回值包装成一个resolved状态的Promise,然后将它的.then回调推入微任务队列。
完整的主线程与队列执行顺序是:
- 执行
console.log("start")→ 输出"start" - 调用
fetch:浏览器后台发起网络请求(属于宏任务触发源),同时注册第一个.then回调 - 调用
longTask():同步执行整个循环,阻塞主线程7-8秒 - 循环结束,
longTask返回resolved Promise,将它的.then回调推入微任务队列 - 执行
console.log("end")→ 输出"end" - 当前调用栈清空,开始处理微任务队列:先执行
longTask的.then回调 → 输出"completed a long operation" - 微任务队列清空后,事件循环处理宏任务队列:此时
fetch的网络请求已完成,对应的回调被执行,触发第一个.then(解析response),response.json()返回的Promise又会将其回调推入微任务队列,最终输出API返回的JSON对象。
3. 你的理解误区
- 误以为
async函数调用会直接进入宏任务队列,但实际上async函数的调用是同步的,只有它内部的异步逻辑(比如await)或者返回Promise后的.then回调才会进入微任务队列。 fetch的网络请求是浏览器发起的异步操作(属于宏任务),但它的.then回调是微任务。不过因为longTask阻塞了主线程,在longTask执行的7-8秒里,浏览器已经完成了网络请求,但主线程被占用时事件循环无法处理任何队列任务,直到longTask执行完、主线程空闲后才会开始处理队列。
内容的提问来源于stack exchange,提问作者Lakshay Dutta
相关产品推荐
相关产品推荐

