JavaScript 异步编程深度解析:Promise、Async/Await 与事件循环

为什么 JavaScript 需要异步?

JavaScript 是单线程语言,同一时间只能执行一个任务。如果所有操作都是同步的,当遇到网络请求、文件读取等耗时操作时,整个程序会被阻塞。异步编程就是为了解决这个问题——让耗时操作在后台执行,不阻塞主线程。

从回调函数说起

最初的异步方案是回调函数(Callback),但它容易导致"回调地狱":

// 回调地狱 —— 代码横向发展,难以维护
getUser(userId, function(user) {
  getOrders(user.id, function(orders) {
    getOrderDetail(orders[0].id, function(detail) {
      getShipping(detail.shippingId, function(shipping) {
        console.log(shipping.status)
        // 更多嵌套...
      })
    })
  })
})

回调函数的问题:嵌套层级深、错误处理分散、代码可读性差。

Promise —— 异步编程的革命

Promise 是 ES6 引入的异步解决方案,它用链式调用取代了嵌套回调。

Promise 的三种状态

状态说明是否可变
pending初始状态,既未完成也未拒绝→ fulfilled 或 rejected
fulfilled操作成功完成不可变
rejected操作失败不可变

创建 Promise

function fetchData(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest()
    xhr.open('GET', url)
    xhr.onload = () => {
      if (xhr.status >= 200 && xhr.status < 300) {
        resolve(JSON.parse(xhr.responseText))
      } else {
        reject(new Error(`请求失败: ${xhr.status}`))
      }
    }
    xhr.onerror = () => reject(new Error('网络错误'))
    xhr.send()
  })
}

链式调用

// 链式调用 —— 扁平化,清晰易读
getUser(userId)
  .then(user => getOrders(user.id))
  .then(orders => getOrderDetail(orders[0].id))
  .then(detail => getShipping(detail.shippingId))
  .then(shipping => console.log(shipping.status))
  .catch(error => console.error('出错了:', error))

实用的 Promise 静态方法

// Promise.all —— 并行执行,全部成功才算成功
const [user, config, theme] = await Promise.all([
  fetchUser(),
  fetchConfig(),
  fetchTheme()
])

// Promise.allSettled —— 等待全部完成,不管成功失败
const results = await Promise.allSettled([
  fetch('/api/fast'),   // 可能成功
  fetch('/api/slow'),   // 可能超时
  fetch('/api/unstable') // 可能失败
])
results.forEach(r => {
  if (r.status === 'fulfilled') console.log('成功:', r.value)
  else console.log('失败:', r.reason)
})

// Promise.race —— 返回最先完成的结果
const result = await Promise.race([
  fetch('/api/data'),
  new Promise((_, reject) =>
    setTimeout(() => reject(new Error('超时')), 5000)
  )
])

// Promise.any —— 返回最先成功的结果
const fastest = await Promise.any([
  fetch('/mirror1/api'),
  fetch('/mirror2/api'),
  fetch('/mirror3/api')
])

Async/Await —— 优雅的异步语法

Async/Await 是 ES2017 引入的语法糖,让异步代码看起来像同步代码一样自然。

// async 函数总是返回 Promise
async function getUserShipping(userId) {
  const user = await getUser(userId)
  const orders = await getOrders(user.id)
  const detail = await getOrderDetail(orders[0].id)
  const shipping = await getShipping(detail.shippingId)
  return shipping.status
}

// 使用 try/catch 处理错误
async function safeFetchData(url) {
  try {
    const response = await fetch(url)
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`)
    }
    return await response.json()
  } catch (error) {
    console.error('请求失败:', error.message)
    return null
  } finally {
    console.log('请求完成')  // 无论成功失败都会执行
  }
}

并行执行优化

// ❌ 错误:串行执行,总耗时 = t1 + t2 + t3
async function slow() {
  const a = await fetch('/api/a')  // 2秒
  const b = await fetch('/api/b')  // 3秒
  const c = await fetch('/api/c')  // 1秒
  // 总耗时 6 秒
}

// ✅ 正确:并行执行,总耗时 = max(t1, t2, t3)
async function fast() {
  const [a, b, c] = await Promise.all([
    fetch('/api/a'),
    fetch('/api/b'),
    fetch('/api/c')
  ])
  // 总耗时 3 秒(取最长的)
}

深入事件循环

理解异步的关键在于理解事件循环(Event Loop)。JavaScript 的执行模型基于以下概念:

调用栈、任务队列与微任务

console.log('1. 同步')           // 调用栈

setTimeout(() => {
  console.log('4. 宏任务')       // 宏任务队列(Task Queue)
}, 0)

Promise.resolve().then(() => {
  console.log('3. 微任务')       // 微任务队列(Microtask Queue)
})

console.log('2. 同步')

// 输出顺序: 1 → 2 → 3 → 4

事件循环的执行顺序:

  1. 执行调用栈中的同步代码
  2. 调用栈清空后,执行所有微任务(Promise.then、queueMicrotask)
  3. 取一个宏任务执行(setTimeout、setInterval、I/O)
  4. 重复步骤 2-3
// 经典面试题:输出顺序
async function async1() {
  console.log('async1 start')
  await async2()
  console.log('async1 end')    // 微任务
}

async function async2() {
  console.log('async2')
}

console.log('script start')

setTimeout(() => {
  console.log('setTimeout')    // 宏任务
}, 0)

async1()

new Promise((resolve) => {
  console.log('promise1')
  resolve()
}).then(() => {
  console.log('promise2')      // 微任务
})

console.log('script end')

// 输出:
// script start → async1 start → async2 → promise1 → script end
// → async1 end → promise2 → setTimeout

错误处理最佳实践

// 工具函数:包装 async 函数,返回 [error, data]
async function to(promise) {
  try {
    const data = await promise
    return [null, data]
  } catch (error) {
    return [error, null]
  }
}

// 使用方式
async function handleRequest() {
  const [err, user] = await to(fetchUser(id))
  if (err) {
    showError('用户加载失败')
    return
  }

  const [err2, orders] = await to(fetchOrders(user.id))
  if (err2) {
    showError('订单加载失败')
    return
  }

  renderOrders(orders)
}

总结

在实际开发中,推荐使用 Async/Await 处理异步逻辑,配合 Promise.all 实现并行请求,用 try/catch 进行错误处理。理解事件循环机制,能帮助你写出更高效、更可靠的异步代码。