# async/await使用
30 秒速记
async函数总是返回Promise;await等一个Promise有结果再往下走await只暂停当前这个async函数,事件循环照转,别的请求照样处理- 互不依赖的请求别连着
await,用Promise.all并发;有依赖的才串行 - 错误用
try/catch接,但只接得住被await的;没await的Promise报错会漏掉 - 同步的
CPU密集计算照样卡死整个进程,await救不了
async/await 让异步代码写起来像同步,但它只暂停当前这个函数,不会卡住整个线程。 你可以把 await 理解成「这个函数先挂起,结果回来了再接着往下执行」,挂起期间事件循环继续处理其他回调和请求。它其实是 Promise 的语法糖,async 函数一定返回 Promise。最常见的问题是把本来能并发的请求写成一串 await,白白多等;另一个是忘了 await,错误就跑出 try/catch 了。
async function slow() {
const a = await fetchA() // 等 1s
const b = await fetchB() // 再等 1s,总共 2s
}
async function fast() {
const [a, b] = await Promise.all([fetchA(), fetchB()]) // 同时发,总共 1s
}
理解「async/await使用」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。
工程中还要验证退出码、资源清理和异常输入。属于 event-loop 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。
async/await 是 Promise 的语法糖,用一段代码就能看清它的执行时机:
console.log('A')
async function run() {
console.log('B')
await null // 从这里挂起,后面的代码进微任务队列
console.log('D')
}
run()
console.log('C')
// 输出:A B C D
run() 调用时同步执行到第一个 await,打印 B;遇到 await 函数挂起,控制权回到调用方,所以先打印 C;同步代码跑完后,微任务队列里的「await 后续」执行,打印 D。这说明 await 只挂起当前函数,并没有阻塞外面的代码。
错误处理是第二个要点。await 一个被拒绝的 Promise 会在那一行抛出异常,所以可以用 try/catch:
async function load() {
try {
const res = await fetch('/api/user')
if (!res.ok) throw new Error('HTTP ' + res.status)
return await res.json()
} catch (err) {
console.error('加载失败', err.message)
return null
}
}
注意 return await 在 try 里是有意义的:只写 return res.json() 的话,json() 解析失败的错误不会被这个 catch 接住。
第三个要点是并发。连续写 await a(); await b() 是串行的,两个互不依赖的请求应该先同时发出去,再一起等:await Promise.all([a(), b()])。如果并发数量可能很大(比如几千个文件),就要限制同时进行的数量,避免把下游打挂。
面试官追问
追问 1下面这段先打印什么?setTimeout(() => console.log(1)); (async () => { await null; console.log(2) })(); console.log(3)
3 2 1。同步的 3 先出;await 之后的代码进微任务,同步代码跑完立刻执行,打印 2;setTimeout 是宏任务,最后打印 1。
追问 2try { doSave() } catch (e) {},doSave 是 async 的,里面抛错能接住吗?
接不住,没 await 的话 doSave() 只是返回一个被拒绝的 Promise,try 块早就结束了,最后变成 unhandledRejection。改成 await doSave()。
追问 3一个页面要拉用户信息、订单、推荐三个接口,互不依赖,怎么写最快?
const [user, orders, recs] = await Promise.all([getUser(), getOrders(), getRecs()])。如果某个失败了也想展示其他的,用 Promise.allSettled,再逐个看 status。
追问 4接口里 await 了一个 5s 的 CPU 计算函数,其他请求会被卡住吗?
会,如果那个函数本身是同步算的(比如大循环、JSON.parse 巨大字符串),包成 async 也没用,主线程被占着谁都跑不了。要么拆片让出事件循环,要么丢给 worker_threads。
追问 5用户点了取消,正在 await 的请求能停下来吗?
Promise 本身没有取消机制,要靠 AbortController:把 signal 传给 fetch(url, { signal }),取消时 controller.abort(),await 那里会抛 AbortError。
# async/await使用
# 快速上手理解
30 秒速记
getSyncTime返回Promise,500ms后resolve出耗时 → 最底层是PromisegetSyncDataawait它再拼字符串 →getDataawait再打印,一层层往上等- 每一层
async函数都返回Promise,所以上层能接着await setTimeout回调里抛的错,外面那层try/catch接不住,要在回调里自己reject- 定时器只保证「至少等这么久」,打印出来可能是
501、503,别断言正好500
这段例子演示的是 await 一层层等结果:最底层用 Promise 包一个定时器,上面两层 async 函数逐级 await,最后打印出耗时。 跑完你会看到大约 500ms 后控制台输出 endTime - startTime = 501 这样的数字。读这段代码要注意两点:一是 getSyncTime 里那个 try/catch 其实只包住了创建定时器的同步代码,回调里出错它管不着;二是三层函数虽然写法像同步,getData() 调用后会立刻返回一个 Promise,后面的代码不会等它。
function getSyncTime() {
return new Promise((resolve, reject) => {
const start = Date.now()
setTimeout(() => {
try {
resolve(Date.now() - start) // 回调内部自己兜错
} catch (err) {
reject(err)
}
}, 500)
})
}
先复制以下这段代码,在粘贴在chrome的控制台console中,按回车键执行
function getSyncTime() {
return new Promise((resolve, reject) => {
try {
let startTime = new Date().getTime()
setTimeout(() => {
let endTime = new Date().getTime()
let data = endTime - startTime
resolve( data )
}, 500)
} catch ( err ) {
reject( err )
}
})
}
async function getSyncData() {
let time = await getSyncTime()
let data = `endTime - startTime = ${time}`
return data
}
async function getData() {
let data = await getSyncData()
console.log( data )
}
getData()
面试官追问
追问 1getData() 下一行写 console.log('end'),end 先打印还是耗时先打印?
end 先打印。getData() 跑到第一个 await 就挂起返回了,后面的同步代码继续执行,500ms 后才轮到打印耗时。
追问 2测试里断言耗时 === 500,偶尔失败,为什么?
setTimeout 的时间是「最少等多久」,主线程忙或者标签页在后台都会推迟回调,后台标签页甚至会被节流到 1s 以上。断言 >= 500 加一个合理上限就行。
