Skip to content

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 当成“同步阻塞”;它只是暂停当前异步函数。
  • 不是所有错误都会自动变成异常,例如 fetch404500 默认仍需手动判断 response.ok

基于 MIT 许可发布