主题
async / await
async / await 是建立在 Promise 之上的语法糖。它没有改变异步模型本身,只是把原本通过 then / catch 组织的流程,改写成更接近同步代码的形式。
为什么会有 async / await
当异步步骤较多时,只用 Promise 链虽然已经比回调更稳定,但可读性仍然可能下降:
js
getUser()
.then((user) => {
return getOrders(user.id)
})
.then((orders) => {
return getInvoice(orders[0].id)
})
.then((invoice) => {
console.log(invoice)
})
.catch((error) => {
console.error(error)
})改写为 async / await 后,控制流会更接近“按步骤执行”的思路:
js
async function showInvoice() {
try {
const user = await getUser()
const orders = await getOrders(user.id)
const invoice = await getInvoice(orders[0].id)
console.log(invoice)
} catch (error) {
console.error(error)
}
}async 做了什么
async 用来声明一个异步函数。它有两个最关键的语义:
async函数总是返回一个 Promise。- 函数内部
return的普通值,会被自动包装成成功 Promise。
js
async function getValue() {
return 1
}
getValue().then((value) => {
console.log(value) // 1
})如果函数内部抛出错误,返回的 Promise 会变为 rejected:
js
async function getValue() {
throw new Error('failed')
}
getValue().catch((error) => {
console.error(error.message) // failed
})await 做了什么
await 会等待一个 Promise 敲定,并拿到它的成功结果。
js
async function loadUser() {
const response = await fetch('/api/user')
const user = await response.json()
return user
}如果 await 后面不是 Promise,也会先被当作 Promise.resolve(value) 处理:
js
async function demo() {
const value = await 1
console.log(value) // 1
}await 只能出现在:
async函数内部- ES Module 的顶层作用域中,也就是 Top-level
await
错误处理
await 等到的是失败 Promise 时,会把失败原因当作异常抛出,因此最常见的写法是配合 try...catch:
js
async function requestUser() {
try {
const response = await fetch('/api/user')
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}
return await response.json()
} catch (error) {
console.error('request failed:', error)
throw error
}
}这里有两个容易混淆的点:
return value:返回一个成功 Promise。throw error:返回一个失败 Promise。
js
async function ok() {
return 'ok'
}
async function fail() {
throw new Error('failed')
}和 Promise 链的关系
async / await 不是 Promise 的替代品,而是另一种组织 Promise 的写法。
js
function loadUser() {
return fetch('/api/user')
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}
return response.json()
})
.then((user) => {
return user.name
})
}js
async function loadUser() {
const response = await fetch('/api/user')
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}
const user = await response.json()
return user.name
}两者的底层模型一致,差异主要在表达方式:
- Promise 链适合直接做链式组合。
async / await更适合按步骤书写流程。- 需要并发时,不能把多个
await机械地串行展开。
串行与并发
下面写法会按顺序等待两个请求:
js
async function load() {
const user = await getUser()
const settings = await getSettings()
return { user, settings }
}如果两者互不依赖,更适合先发起、再统一等待:
js
async function load() {
const userPromise = getUser()
const settingsPromise = getSettings()
const [user, settings] = await Promise.all([userPromise, settingsPromise])
return { user, settings }
}和事件循环的关系
await 不会阻塞整个 JavaScript 线程,它只会暂停当前 async 函数后续语句的执行,并在 Promise 敲定后继续恢复。
js
console.log(1)
Promise.resolve().then(() => {
console.log(2)
})
;(async () => {
await Promise.resolve()
console.log(3)
})()
console.log(4)输出顺序通常是:
js
1
4
2
3这是因为 await 之后的继续执行逻辑,同样会进入微任务队列。更完整内容见 /web/core/ecmascript/promise/promise-event-loop。
使用建议
- 需要把异步逻辑写成顺序步骤时,优先考虑
async / await。 - 需要并发多个独立任务时,优先结合
Promise.all等静态方法。 - 不要把
await当成“同步阻塞”;它只是暂停当前异步函数。 - 不是所有错误都会自动变成异常,例如
fetch的404、500默认仍需手动判断response.ok。
