Skip to content

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 的优势

  1. 代码可读性更好:同步风格更直观
  2. 错误处理统一:使用 try-catch 而非多个 catch
  3. 调试更方便:可以在 await 处打断点
  4. 条件逻辑清晰:if-else 更自然

需要注意的问题

  1. 并发性能:多个独立的 await 会串行执行,应该用 Promise.all 优化
  2. 错误处理:忘记加 try-catch 会导致未捕获的 Promise rejection
  3. 兼容性:老浏览器需要转译

实际应用中,我经常使用:

  • 请求重试(指数退避)
  • 超时控制(Promise.race)
  • 批量处理(限制并发数)
  • 缓存优化(避免重复请求)

最佳实践是:顶层用 async/await,底层用 Promise,结合两者的优势。


八、记忆口诀

async/await 歌诀:

async 函数返 Promise,
await 只能在它里。
异步代码同步写,
try-catch 来处理!

独立请求要并行,
Promise.all 来帮忙。
错误别忘加 catch,
否则报错 Unhandled!

Generator 加 Promise,
语法糖来真给力。
调试方便易理解,
异步编程终极致!

九、推荐资源


十、总结一句话

  • async/await: 同步风格 + 易于理解 = 异步编程终极方案 🎯
  • 错误处理: try-catch + 统一捕获 = 更健壮的代码
  • 并发优化: Promise.all + 批量处理 = 性能提升关键
最近更新