调用async函数后用await等待响应,它和同步函数有何区别?
很多刚接触异步编程的开发者都会有这个疑问,核心是踩了「调用async就必须立刻await」的认知误区,两者的本质差异其实非常明确:
最核心的区别:await的「等待」是非阻塞的,同步函数的等待是阻塞的
同步函数执行时会独占当前线程,只要它没跑完,线程什么别的任务都处理不了:浏览器环境里会直接冻住页面,点击、滚动、渲染全部卡住;Node.js服务环境里会直接暂停所有其他请求的处理,直到同步逻辑执行完。
而async函数碰到await关键字时,只会暂停当前async函数内部的执行,立刻把线程的执行权交出去,等待异步操作(网络请求、文件IO、定时器等)完成的这段空窗期,线程完全可以去处理其他待执行的任务,等异步操作有结果了,再回来接着跑await后面的代码。
你可以跑下面这段代码直观感受差异:// 同步阻塞示例 function syncBlock() { const start = Date.now(); while(Date.now() - start < 3000) {} // 空转3秒模拟阻塞 console.log("同步阻塞结束"); } console.log("=== 调用同步方法 ==="); syncBlock(); console.log("这行必须等3秒卡死之后才会打印"); // 异步非阻塞示例 function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function asyncNonBlock() { console.log("进入async函数,开始等待3秒"); await delay(3000); console.log("异步等待结束"); } console.log("=== 调用异步方法 ==="); asyncNonBlock(); console.log("这行会立刻打印,根本不会卡3秒");async函数不需要在调用时立刻await,天然支持并发任务调度
同步函数只要被调用,就必须等它完全执行完才能走下一步,根本没机会同时发起多个任务。但async函数返回的是Promise对象,你完全可以先同时发起多个互不依赖的异步任务,最后再统一等待结果,把串行等待的耗时压到最低。
比如页面初始化要拉3个互不依赖的接口,错误写法和正确写法的性能差一倍以上:async function fetchUser() { /* 耗时1s */ } async function fetchOrder() { /* 耗时2s */ } async function fetchRecommend() { /* 耗时1.5s */ } // 错误写法:串行await,总耗时1+2+1.5=4.5s async function badInit() { const user = await fetchUser(); const order = await fetchOrder(); const recommend = await fetchRecommend(); renderPage(user, order, recommend); } // 正确写法:先并发发起请求再统一等待,总耗时≈2s(取最长任务耗时) async function goodInit() { // 先同时发起三个请求,不做等待 const userP = fetchUser(); const orderP = fetchOrder(); const recommendP = fetchRecommend(); // 需要用结果的时候再等所有任务完成 const [user, order, recommend] = await Promise.all([userP, orderP, recommendP]); renderPage(user, order, recommend); }这种调度能力是同步函数完全不可能实现的。
async函数兼顾了异步性能和同步代码的可读性
早年没有async/await的时候,写异步逻辑要么套好几层回调形成回调地狱,要么手动写Promise链式调用,代码可读性很差。async/await本质是语法糖,你可以用写同步代码的线性逻辑写异步流程,错误处理直接用try/catch就能搞定,不用写一堆嵌套的错误判断,同时完全保留了异步非阻塞的性能优势。
最后澄清一个常见误区:await从来不会把异步代码变成同步代码,它只是让当前async函数内部的执行流程看起来是线性的,绝对不会阻塞函数外部的代码执行,这是很多新手最容易搞混的点。
内容的提问来源于stack exchange,提问作者Giackkk

