
本文解析 async 函数并非天然“异步执行”,其内部若无真正的异步操作(如 await、setTimeout、fetch 等),仍会同步占用主线程;而浏览器 I/O 类异步操作(如 fetch)则由底层线程处理,不阻塞 JS 主线程。
本文解析 `async` 函数并非天然“异步执行”,其内部若无真正的异步操作(如 `await`、`settimeout`、`fetch` 等),仍会同步占用主线程;而浏览器 i/o 类异步操作(如 `fetch`)则由底层线程处理,不阻塞 js 主线程。
在 JavaScript 中,“async” 一词常被误解为“自动异步执行”。实际上,async function 仅表示该函数返回一个 Promise,并允许使用 await;它本身不引入任何异步延迟或线程切换。真正的异步行为取决于函数体内部是否调用了宿主环境提供的异步原语(如 fetch、setTimeout、WebSocket、IndexedDB 等)。
以你提供的 longTask() 为例:
async function longTask() {
let i = 0;
while (true) {
i++;
if (i === 6_000_000_000) {
return "completed a long operation";
}
}
}
尽管标记为 async,该函数完全同步运行:
- 调用
longTask()立即进入执行上下文,while循环在主线程上持续占用 CPU,阻塞事件循环; - 直到循环结束,函数才返回一个 已决议(fulfilled)的 Promise;
- 此时
.then(...)注册的回调被作为微任务(microtask) 加入微任务队列,待当前同步代码(含console.log("end"))执行完毕后立即执行。
而 fetch('...') 的行为截然不同:
-
fetch是浏览器提供的 Web API,其网络请求交由底层线程(如网络线程)异步处理; - JS 主线程几乎立刻返回一个 pending Promise,不等待响应;
- 当响应就绪后,浏览器将
fetch().then(...)的回调作为宏任务(macrotask) 推入宏任务队列(注意:现代规范中fetch的 resolve 回调实际触发微任务,但其 I/O 完成通知本身依赖宏任务调度机制,整体延迟显著高于纯计算任务); - 因此,即使
fetch实际完成得更早,其回调也需等待当前同步栈清空 + 所有微任务执行完毕后,才在下一轮事件循环中执行。
这就是你观察到输出顺序的原因:
start
end ← 同步代码(含 longTask 的漫长计算)结束后才打印
completed a long operation ← longTask.then():微任务,紧随同步代码之后
{ API call result } ← fetch.then():相对延迟的宏任务(或受网络/解析影响的微任务)
✅ 关键结论:
-
async≠ 异步执行,只 = 返回 Promise + 支持await; - 真正的异步 = 依赖浏览器/Node.js 运行时提供的非阻塞系统调用;
- 长时间同步计算(如密集循环、大数组排序)会阻塞主线程,应避免;必要时可用
setTimeout(() => {}, 0)或queueMicrotask()拆分任务,或迁移到 Web Worker。
⚠️ 注意事项:
- 不要滥用
async包裹纯计算逻辑——它无法缓解阻塞; - 若需模拟“延迟 resolve”,应使用
await new Promise(r => setTimeout(r, ms)),而非空转 CPU; -
fetch、Promise.then、MutationObserver属于微任务优先级;setTimeout、setInterval、I/O 回调属于宏任务,优先级更低。
理解这一区别,是写出高性能、响应式前端应用的基础。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











