async/await 面试题全解析
一、核心要点速览
💡 核心考点
- 本质: Generator + Promise 的语法糖
- 特点: 让异步代码看起来像同步代码
- 错误处理: try-catch 捕获异常
- 并发优化: Promise.all 实现并行
二、async/await 基础
1. 基本语法
javascript
// async 函数总是返回 Promise
async function fn() {
return 'hello'
// 等价于 Promise.resolve('hello')
}
fn().then(console.log) // 'hello'
// await 只能在 async 函数中使用
async function getData() {
const user = await fetch('/api/user')
const posts = await fetch('/api/posts')
return { user, posts }
}
// 箭头函数
const getData = async () => {
const res = await fetch('/api/data')
return res.json()
}2. 与 Promise 对比
javascript
// Promise 链式
fetchData()
.then(data => processData(data))
.then(result => saveResult(result))
.then(() => console.log('完成'))
.catch(error => console.error(error))
// async/await
async function handleData() {
try {
const data = await fetchData()
const result = await processData(data)
await saveResult(result)
console.log('完成')
} catch (error) {
console.error(error)
}
}
// 对比:
// ✓ await 更直观,像同步代码
// ✓ 错误处理统一(try-catch)
// ✓ 调试更容易(可以打断点)
// ✗ 无法利用并行性(需要注意优化)3. 执行流程图
时间 → ─────────────────────────────────────────────────►
async/await 执行流程:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
调用 async 函数
│
▼
┌─────────────────┐
│ 返回 Promise │ ← async 总是返回 Promise
└────────┬────────┘
│
▼
┌─────────┐
│ pending │
└─────────┘
│
│ 执行函数体
▼
┌─────────────┐
│ await expr │ ← 暂停执行
└──────┬──────┘
│
│ 等待 Promise settle
▼
┌─────────────┐
│ fulfilled │ ← 恢复执行
│ value: data │
└─────────────┘
│
▼
继续执行后续代码
│
▼
┌─────────────┐
│ return │ ← 返回值被包装
└─────────────┘
│
▼
resolve(value)
关键点:
✓ await 暂停函数执行
✓ 等待 Promise settled
✓ 自动解包 Promise
✓ 返回值自动包装
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━三、错误处理
1. try-catch 方式
javascript
// 推荐方式:try-catch
async function getUser() {
try {
const user = await fetch('/api/user')
const data = await user.json()
return data
} catch (error) {
console.error('获取用户失败:', error)
throw error // 可以继续传播
}
}
// 多个 await 的错误处理
async function processAll() {
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)
return null
}
}2. .catch() 方式
javascript
// 方式 2:Promise 风格的错误处理
async function getUser() {
const user = await fetch('/api/user').catch(err => {
console.error('请求失败:', err)
return null // 返回默认值
})
if (!user) return null
const data = await user.json()
return data
}
// 混合使用
async function handleData() {
try {
const data = await fetchData()
const result = await processData(data).catch(err => {
console.error('处理失败:', err)
return null
})
if (!result) throw new Error('无数据')
return result
} catch (error) {
console.error('整体失败:', error)
}
}3. 错误处理最佳实践
javascript
// ✓ 最佳实践:分层错误处理
async function fetchUser(id) {
try {
const res = await fetch(`/api/users/${id}`)
if (!res.ok) throw new Error(`HTTP ${res.status}`)
return await res.json()
} catch (error) {
console.error(`获取用户${id}失败:`, error)
throw error // 向上层传播
}
}
async function displayUser(id) {
try {
const user = await fetchUser(id)
renderUser(user)
} catch (error) {
showError('无法加载用户信息')
// 不再继续传播
}
}
// ✗ 避免:吞掉所有错误
async function badExample() {
try {
await doSomething()
} catch (error) {
// 什么都不做 ❌
}
}四、并发优化
1. 串行 vs 并行
javascript
// ❌ 错误写法(串行,耗时 2 倍)
async function getUserData(userId) {
const user = await fetchUser(userId) // 100ms
const posts = await fetchPosts(userId) // 100ms
// 总耗时:200ms
return { user, posts }
}
// ✓ 正确写法(并行,耗时 1 倍)
async function getUserData(userId) {
const [user, posts] = await Promise.all([
fetchUser(userId), // 100ms
fetchPosts(userId) // 100ms
])
// 总耗时:100ms
return { user, posts }
}
// 性能对比可视化:
// 串行:███████████████████████ 200ms
// 并行:███████████ 100ms
//
// 性能提升:50% ⚡2. 部分并行场景
javascript
// 场景:依赖前一个结果,但后续可以并行
async function getDashboardData(userId) {
// 必须先获取用户
const user = await fetchUser(userId)
// 然后可以并发获取其他数据
const [posts, followers, following] = await Promise.all([
fetchPosts(user.id),
fetchFollowers(user.id),
fetchFollowing(user.id)
])
return { user, posts, followers, following }
}
// 时序图:
// 时间 → ─────────────────────────────────────────►
//
// fetchUser ████████
// ├─ fetchPosts ████
// ├─ fetchFollowers ████
// └─ fetchFollowing ████
//
// 总耗时 = user(100ms) + max(posts, followers, following)(50ms)
// = 150ms
//
// 如果全部串行:100 + 50 + 50 + 50 = 250ms
// 优化后节省:40% ⚡3. 批量处理优化
javascript
// 场景:处理大量数据
async function processLargeBatch(items) {
// ❌ 全部并发(可能内存爆炸)
const results = await Promise.all(
items.map(item => processItem(item))
)
// ✓ 分批处理(每批 10 个)
const BATCH_SIZE = 10
const results = []
for (let i = 0; i < items.length; i += BATCH_SIZE) {
const batch = items.slice(i, i + BATCH_SIZE)
const batchResults = await Promise.all(
batch.map(item => processItem(item))
)
results.push(...batchResults)
}
return results
}
// ✓ 使用 p-limit 库控制并发数
import pLimit from 'p-limit'
async function processWithLimit(items) {
const limit = pLimit(5) // 最多 5 个并发
const promises = items.map(item =>
limit(() => processItem(item))
)
return await Promise.all(promises)
}五、实际应用场景
1. 请求重试
javascript
// 带重试的请求
async function fetchWithRetry(url, options = {}, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const res = await fetch(url, options)
if (res.ok) return await res.json()
throw new Error(`HTTP ${res.status}`)
} catch (error) {
if (i === retries - 1) throw error
// 等待指数退避
const delay = Math.pow(2, i) * 1000
await new Promise(resolve => setTimeout(resolve, delay))
}
}
}
// 使用
try {
const data = await fetchWithRetry('/api/data')
console.log(data)
} catch (error) {
console.error('多次重试后仍然失败:', error)
}2. 请求超时
javascript
// 带超时的请求
async function fetchWithTimeout(url, timeout = 5000) {
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), timeout)
try {
const res = await fetch(url, {
signal: controller.signal
})
return await res.json()
} finally {
clearTimeout(timeoutId)
}
}
// 或者使用 Promise.race
async function fetchWithTimeout(url, timeout = 5000) {
const fetchPromise = fetch(url).then(r => r.json())
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(`请求超时 (${timeout}ms)`))
}, timeout)
})
return await Promise.race([fetchPromise, timeoutPromise])
}3. 队列顺序执行
javascript
// 按顺序执行任务
async function runQueue(tasks) {
const results = []
for (const task of tasks) {
try {
const result = await task()
results.push({ success: true, data: result })
} catch (error) {
results.push({ success: false, error })
}
}
return results
}
// 使用
const tasks = [
() => fetch('/api/1'),
() => fetch('/api/2'),
() => fetch('/api/3')
]
const results = await runQueue(tasks)六、进阶技巧
1. 并行中的串行依赖
javascript
// 场景:部分有依赖,部分可并行
async function getComplexData(userId) {
// 无依赖的可以并行
const [config, settings] = await Promise.all([
fetchConfig(),
fetchSettings()
])
// 需要 config 的
const data = await fetchData(config.id)
// 需要 data 的
const details = await fetchDetails(data.ids)
return { config, settings, data, details }
}2. 取消异步操作
javascript
// 使用 AbortController
async function cancellableFetch(url, signal) {
try {
const res = await fetch(url, { signal })
return await res.json()
} catch (error) {
if (error.name === 'AbortError') {
console.log('请求已取消')
} else {
throw error
}
}
}
// 使用
const controller = new AbortController()
// 开始请求
const promise = cancellableFetch('/api/data', controller.signal)
// 需要时取消
controller.abort()3. 缓存优化
javascript
// 带缓存的 async 函数
const cache = new Map()
async function fetchWithCache(key, url, ttl = 60000) {
const cached = cache.get(key)
if (cached && Date.now() - cached.timestamp < ttl) {
return cached.data
}
const data = await fetch(url).then(r => r.json())
cache.set(key, { data, timestamp: Date.now() })
return data
}
// 使用
const userData = await fetchWithCache(
'user:123',
'/api/users/123'
)七、面试标准回答
async/await 是 ES2017 引入的异步编程语法,基于 Promise 和 Generator,让异步代码看起来像同步代码。
本质是 Generator + Promise 的语法糖。async 函数总是返回 Promise,await 表达式会暂停函数执行,等待 Promise settled 后恢复执行并自动解包结果。
相比 Promise 的优势:
- 代码可读性更好:同步风格更直观
- 错误处理统一:使用 try-catch 而非多个 catch
- 调试更方便:可以在 await 处打断点
- 条件逻辑清晰:if-else 更自然
需要注意的问题:
- 并发性能:多个独立的 await 会串行执行,应该用 Promise.all 优化
- 错误处理:忘记加 try-catch 会导致未捕获的 Promise rejection
- 兼容性:老浏览器需要转译
实际应用中,我经常使用:
- 请求重试(指数退避)
- 超时控制(Promise.race)
- 批量处理(限制并发数)
- 缓存优化(避免重复请求)
最佳实践是:顶层用 async/await,底层用 Promise,结合两者的优势。
八、记忆口诀
async/await 歌诀:
async 函数返 Promise,
await 只能在它里。
异步代码同步写,
try-catch 来处理!
独立请求要并行,
Promise.all 来帮忙。
错误别忘加 catch,
否则报错 Unhandled!
Generator 加 Promise,
语法糖来真给力。
调试方便易理解,
异步编程终极致!九、推荐资源
十、总结一句话
- async/await: 同步风格 + 易于理解 = 异步编程终极方案 🎯
- 错误处理: try-catch + 统一捕获 = 更健壮的代码 ✓
- 并发优化: Promise.all + 批量处理 = 性能提升关键 ⚡