Skip to content

Promise & async/await

异步编程是前端开发的核心技能。从回调地狱到 Promise 链,再到 async/await,理解每一层抽象的底层原理。

回调地狱的问题

js
// ❌ 回调地狱:难以阅读、错误处理分散
getUser(userId, (err, user) => {
  if (err) return handleError(err)
  getOrders(user.id, (err, orders) => {
    if (err) return handleError(err)
    getOrderDetails(orders[0].id, (err, details) => {
      if (err) return handleError(err)
      render(user, orders, details)
    })
  })
})

Promise 基础

js
// Promise 三种状态:pending → fulfilled / rejected
const p = new Promise((resolve, reject) => {
  // 异步操作
  setTimeout(() => {
    if (Math.random() > 0.5) {
      resolve('成功')   // 状态变为 fulfilled
    } else {
      reject(new Error('失败'))  // 状态变为 rejected
    }
  }, 1000)
})

p.then(value => console.log(value))
 .catch(err => console.error(err))
 .finally(() => console.log('无论成功失败都执行'))

Promise 链式调用

js
// ✅ Promise 链:扁平化异步流程
getUser(userId)
  .then(user => getOrders(user.id))      // 返回新 Promise
  .then(orders => getOrderDetails(orders[0].id))
  .then(details => render(details))
  .catch(err => handleError(err))        // 统一错误处理

.then 的返回值规则:

js
Promise.resolve(1)
  .then(v => v + 1)           // 返回普通值 → 包装成 Promise.resolve(2)
  .then(v => Promise.resolve(v * 2))  // 返回 Promise → 等待它
  .then(v => { throw new Error('oops') })  // 抛出错误 → 变为 rejected
  .catch(err => 'recovered')  // catch 返回值 → 变为 fulfilled
  .then(v => console.log(v))  // 'recovered'

Promise 静态方法

js
// Promise.all — 全部成功才成功,一个失败就失败
const [user, posts, comments] = await Promise.all([
  fetchUser(1),
  fetchPosts(1),
  fetchComments(1)
])
// 并行请求,比串行快 3 倍

// Promise.allSettled — 等待所有完成,不管成功失败
const results = await Promise.allSettled([
  fetchUser(1),
  fetchUser(999),  // 可能失败
  fetchUser(2)
])
results.forEach(result => {
  if (result.status === 'fulfilled') {
    console.log('成功:', result.value)
  } else {
    console.log('失败:', result.reason)
  }
})

// Promise.race — 第一个完成的(成功或失败)
const result = await Promise.race([
  fetch('/api/data'),
  new Promise((_, reject) =>
    setTimeout(() => reject(new Error('超时')), 5000)
  )
])

// Promise.any — 第一个成功的(ES2021)
const fastest = await Promise.any([
  fetch('https://mirror1.example.com/data'),
  fetch('https://mirror2.example.com/data'),
  fetch('https://mirror3.example.com/data')
])
// 返回最快成功的那个,全部失败才抛 AggregateError

async/await 深度解析

js
// async 函数总是返回 Promise
async function fetchData() {
  return 42  // 等价于 return Promise.resolve(42)
}

// await 暂停当前 async 函数,等待 Promise 完成
async function loadUserData(userId) {
  try {
    const user = await fetchUser(userId)       // 等待
    const orders = await fetchOrders(user.id)  // 等待
    return { user, orders }
  } catch (err) {
    console.error('加载失败:', err)
    throw err  // 重新抛出,让调用者处理
  }
}

并行 vs 串行

js
// ❌ 串行(慢):总耗时 = 请求1 + 请求2
async function serial() {
  const user = await fetchUser(1)    // 等 500ms
  const posts = await fetchPosts(1)  // 再等 300ms
  // 总计 800ms
}

// ✅ 并行(快):总耗时 = max(请求1, 请求2)
async function parallel() {
  const [user, posts] = await Promise.all([
    fetchUser(1),   // 同时发起
    fetchPosts(1)   // 同时发起
  ])
  // 总计 500ms
}

// ✅ 先启动,后等待
async function parallelV2() {
  const userPromise = fetchUser(1)   // 立即发起请求
  const postsPromise = fetchPosts(1) // 立即发起请求

  const user = await userPromise     // 等待结果
  const posts = await postsPromise   // 等待结果
}

循环中的异步

js
const ids = [1, 2, 3, 4, 5]

// ❌ forEach 不等待 async 回调
ids.forEach(async (id) => {
  const user = await fetchUser(id)  // forEach 不关心返回的 Promise
  console.log(user)
})
// 顺序不确定,forEach 已经结束了

// ✅ 串行:for...of
for (const id of ids) {
  const user = await fetchUser(id)
  console.log(user)  // 按顺序,一个接一个
}

// ✅ 并行:Promise.all + map
const users = await Promise.all(ids.map(id => fetchUser(id)))

// ✅ 并发控制:限制同时请求数量
async function fetchWithConcurrency(ids, limit = 3) {
  const results = []
  for (let i = 0; i < ids.length; i += limit) {
    const chunk = ids.slice(i, i + limit)
    const chunkResults = await Promise.all(chunk.map(fetchUser))
    results.push(...chunkResults)
  }
  return results
}

手写 Promise

js
class MyPromise {
  #state = 'pending'
  #value = undefined
  #callbacks = []

  constructor(executor) {
    const resolve = (value) => {
      if (this.#state !== 'pending') return
      this.#state = 'fulfilled'
      this.#value = value
      this.#callbacks.forEach(cb => cb.onFulfilled?.(value))
    }

    const reject = (reason) => {
      if (this.#state !== 'pending') return
      this.#state = 'rejected'
      this.#value = reason
      this.#callbacks.forEach(cb => cb.onRejected?.(reason))
    }

    try {
      executor(resolve, reject)
    } catch (err) {
      reject(err)
    }
  }

  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      const handle = (fn, value, fallback) => {
        try {
          if (typeof fn === 'function') {
            const result = fn(value)
            result instanceof MyPromise ? result.then(resolve, reject) : resolve(result)
          } else {
            fallback(value)
          }
        } catch (err) {
          reject(err)
        }
      }

      if (this.#state === 'fulfilled') {
        queueMicrotask(() => handle(onFulfilled, this.#value, resolve))
      } else if (this.#state === 'rejected') {
        queueMicrotask(() => handle(onRejected, this.#value, reject))
      } else {
        this.#callbacks.push({
          onFulfilled: v => handle(onFulfilled, v, resolve),
          onRejected:  v => handle(onRejected, v, reject)
        })
      }
    })
  }

  catch(onRejected) { return this.then(null, onRejected) }
  finally(fn) {
    return this.then(
      v  => MyPromise.resolve(fn()).then(() => v),
      e  => MyPromise.resolve(fn()).then(() => { throw e })
    )
  }

  static resolve(value) {
    return value instanceof MyPromise ? value : new MyPromise(r => r(value))
  }
  static reject(reason) { return new MyPromise((_, r) => r(reason)) }
  static all(promises) {
    return new MyPromise((resolve, reject) => {
      const results = []
      let count = 0
      promises.forEach((p, i) => {
        MyPromise.resolve(p).then(v => {
          results[i] = v
          if (++count === promises.length) resolve(results)
        }, reject)
      })
    })
  }
}

错误处理最佳实践

js
// ✅ 统一错误处理封装
async function safeAsync(promise) {
  try {
    const data = await promise
    return [null, data]
  } catch (err) {
    return [err, null]
  }
}

// 使用:Go 风格错误处理
const [err, user] = await safeAsync(fetchUser(1))
if (err) {
  console.error('获取用户失败:', err)
  return
}
console.log(user)

// ✅ 全局未捕获 Promise 错误
window.addEventListener('unhandledrejection', event => {
  console.error('未处理的 Promise 错误:', event.reason)
  event.preventDefault()  // 阻止默认的控制台错误
})

总结

  • Promise 解决回调地狱,提供链式调用和统一错误处理
  • Promise.all 并行,Promise.allSettled 全部等待,Promise.race 竞速
  • async/await 是 Promise 的语法糖,让异步代码像同步一样可读
  • 循环中用 for...of 串行,Promise.all + map 并行
  • 注意 await 的串行陷阱,独立请求应并行发起

系统学习 Web 前端生态,深入底层架构