Skip to content

async 函数

async 函数是 ES2017 引入的异步编程终极解决方案,让异步代码写起来像同步代码一样直观。

基本语法#

定义 async 函数#

// 函数声明
async function fetchData() {
return '数据'
}
// 函数表达式
const getData = async function () {
return '数据'
}
// 箭头函数
const loadData = async () => {
return '数据'
}
// 方法
const obj = {
async getData() {
return '数据'
},
}
// 类方法
class API {
async fetch() {
return '数据'
}
}

async 函数的返回值#

async 函数总是返回一个 Promise:

async function example() {
return '直接返回值'
}
example().then(console.log) // '直接返回值'
// 返回值会被 Promise.resolve 包装
async function returnPromise() {
return Promise.resolve('已经是 Promise')
}
returnPromise().then(console.log) // '已经是 Promise'
// 抛出错误会变成 rejected 状态
async function throwError() {
throw new Error('出错了')
}
throwError().catch((err) => console.log(err.message)) // '出错了'

await 表达式#

基本用法#

await 只能在 async 函数内部使用,用于等待 Promise 完成:

async function fetchUser() {
// await 暂停执行,等待 Promise 完成
const response = await fetch('/api/user')
const data = await response.json()
return data
}

await 的执行顺序#

async function example() {
console.log('1')
const result = await Promise.resolve('2')
console.log(result)
console.log('3')
}
console.log('start')
example()
console.log('end')
// 输出顺序:
// start
// 1
// end
// 2
// 3

🤔 await 之后的代码会在微任务队列中执行。

await 非 Promise 值#

await 可以用于任何值,非 Promise 值会被自动包装:

async function example() {
const a = await 1 // 等价于 await Promise.resolve(1)
const b = await '字符串'
const c = await { name: '张三' }
console.log(a, b, c)
}
example() // 1 '字符串' { name: '张三' }

thenable 对象#

await 可以处理任何 thenable 对象:

const thenable = {
then(resolve) {
setTimeout(() => resolve('thenable 结果'), 100)
},
}
async function example() {
const result = await thenable
console.log(result) // 'thenable 结果'
}
example()

对比 Promise#

链式调用 vs async/await#

// Promise 链式调用
function fetchUserPosts(userId) {
return fetch(`/api/users/${userId}`)
.then((response) => response.json())
.then((user) => fetch(`/api/posts?userId=${user.id}`))
.then((response) => response.json())
}
// async/await 写法
async function fetchUserPosts(userId) {
const userResponse = await fetch(`/api/users/${userId}`)
const user = await userResponse.json()
const postsResponse = await fetch(`/api/posts?userId=${user.id}`)
return postsResponse.json()
}

条件逻辑#

// Promise 方式处理条件逻辑很麻烦
function getData(condition) {
return fetch('/api/data')
.then((response) => response.json())
.then((data) => {
if (condition) {
return fetch('/api/extra')
.then((r) => r.json())
.then((extra) => ({ ...data, extra }))
}
return data
})
}
// async/await 更清晰
async function getData(condition) {
const response = await fetch('/api/data')
const data = await response.json()
if (condition) {
const extraResponse = await fetch('/api/extra')
const extra = await extraResponse.json()
return { ...data, extra }
}
return data
}

循环中的异步#

// 错误:forEach 不会等待
async function wrong(urls) {
urls.forEach(async (url) => {
const data = await fetch(url)
console.log(data)
})
console.log('完成') // 会先打印
}
// 正确:串行执行
async function sequential(urls) {
for (const url of urls) {
const response = await fetch(url)
console.log(await response.json())
}
console.log('完成')
}
// 正确:并行执行
async function parallel(urls) {
const promises = urls.map((url) => fetch(url).then((r) => r.json()))
const results = await Promise.all(promises)
console.log(results)
console.log('完成')
}

错误处理#

try/catch#

async function fetchData() {
try {
const response = await fetch('/api/data')
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}
return await response.json()
} catch (error) {
console.error('获取数据失败:', error)
throw error // 可以重新抛出
}
}

多个 await 的错误处理#

async function fetchMultiple() {
try {
const user = await fetchUser()
const posts = await fetchPosts(user.id)
const comments = await fetchComments(posts[0].id)
return { user, posts, comments }
} catch (error) {
// 任何一个失败都会到这里
console.error('发生错误:', error)
}
}

单独处理每个 await#

async function fetchWithIndividualHandling() {
let user, posts
try {
user = await fetchUser()
} catch (error) {
console.error('获取用户失败')
user = null
}
try {
posts = await fetchPosts()
} catch (error) {
console.error('获取文章失败')
posts = []
}
return { user, posts }
}

catch 方法结合#

async function fetchData() {
// 使用 catch 提供默认值
const user = await fetchUser().catch(() => null)
const posts = await fetchPosts().catch(() => [])
return { user, posts }
}

并发执行#

Promise.all 并发#

async function fetchAllData() {
// 串行:慢
// const users = await fetchUsers();
// const posts = await fetchPosts();
// 并行:快
const [users, posts] = await Promise.all([fetchUsers(), fetchPosts()])
return { users, posts }
}

Promise.allSettled 容错并发#

async function fetchAllWithFallback() {
const results = await Promise.allSettled([
fetchUsers(),
fetchPosts(),
fetchComments(),
])
return {
users: results[0].status === 'fulfilled' ? results[0].value : [],
posts: results[1].status === 'fulfilled' ? results[1].value : [],
comments: results[2].status === 'fulfilled' ? results[2].value : [],
}
}

竞速#

async function fetchWithTimeout(url, timeout = 5000) {
return Promise.race([
fetch(url),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('请求超时')), timeout)
),
])
}
async function example() {
try {
const response = await fetchWithTimeout('/api/data', 3000)
return response.json()
} catch (error) {
console.error(error.message)
}
}

实战模式#

重试机制#

async function fetchWithRetry(url, maxRetries = 3, delay = 1000) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(url)
if (!response.ok) throw new Error(`HTTP ${response.status}`)
return await response.json()
} catch (error) {
if (attempt === maxRetries) throw error
console.log(`${attempt} 次失败,${delay}ms 后重试...`)
await new Promise((r) => setTimeout(r, delay))
}
}
}

串行队列#

async function processQueue(items, processor) {
const results = []
for (const item of items) {
results.push(await processor(item))
}
return results
}
// 使用
const urls = ['/api/1', '/api/2', '/api/3']
const results = await processQueue(urls, async (url) => {
const response = await fetch(url)
return response.json()
})

限制并发数#

async function asyncPool(limit, items, fn) {
const results = []
const executing = new Set()
for (const item of items) {
const promise = fn(item).then((result) => {
executing.delete(promise)
return result
})
results.push(promise)
executing.add(promise)
if (executing.size >= limit) {
await Promise.race(executing)
}
}
return Promise.all(results)
}
// 最多同时 3 个请求
const results = await asyncPool(3, urls, async (url) => {
const response = await fetch(url)
return response.json()
})

缓存包装器#

function withCache(fn) {
const cache = new Map()
return async function (...args) {
const key = JSON.stringify(args)
if (cache.has(key)) {
return cache.get(key)
}
const result = await fn.apply(this, args)
cache.set(key, result)
return result
}
}
const cachedFetch = withCache(async (url) => {
const response = await fetch(url)
return response.json()
})

顶层 await#

ES2022 允许在模块顶层使用 await:

// config.js (ES Module)
const response = await fetch('/api/config')
export const config = await response.json()
// main.js
import { config } from './config.js'
console.log(config) // 已经是解析后的数据

动态导入#

// 根据条件动态导入模块
const locale = navigator.language
const messages = await import(`./i18n/${locale}.js`)
// 懒加载组件
const { default: Component } = await import('./Component.js')

常见陷阱#

🔶 忘记 await#

async function wrong() {
const data = fetchData() // 忘记 await,data 是 Promise
console.log(data.name) // undefined
}
async function correct() {
const data = await fetchData()
console.log(data.name)
}

🔶 不必要的 await#

// 不必要的 await
async function unnecessary() {
return await fetchData() // await 是多余的
}
// 直接 return 即可
async function correct() {
return fetchData()
}
// 但在 try/catch 中需要 await
async function withTryCatch() {
try {
return await fetchData() // 需要 await 才能捕获错误
} catch (error) {
console.error(error)
}
}

🔶 串行 vs 并行#

// 串行:总耗时 = t1 + t2
async function slow() {
const a = await fetch('/api/a')
const b = await fetch('/api/b')
return [a, b]
}
// 并行:总耗时 = max(t1, t2)
async function fast() {
const [a, b] = await Promise.all([fetch('/api/a'), fetch('/api/b')])
return [a, b]
}

小结#

特性说明
async声明异步函数,返回 Promise
await暂停执行,等待 Promise 完成
错误处理try/catch 或 .catch()
并发Promise.all/allSettled/race
顶层 awaitES2022 模块支持

async/await 是目前最推荐的异步编程方式,让代码更易读、更易维护。