Vue 3 异步请求控制:串行与并行的优雅实践

📅 2026-04-03 17:34:26阅读时间: 17分钟

在 Vue 3 的开发中,处理异步请求是家常便饭。我们经常遇到这样的场景:需要等待一个或多个 API 请求完成后,才能进行下一步的数据处理或页面渲染。如何清晰、高效地控制这些请求的执行顺序,是构建健壮应用的关键。

本文将探讨在 Vue 3 中控制异步请求的两种核心模式:串行执行并行执行,并结合 async/awaitPromise.all 等现代 JavaScript 特性,提供清晰、可维护的解决方案。

🎯 场景一:串行执行,处理请求依赖

当一个请求的执行依赖于前一个请求的返回结果时,我们必须确保它们按顺序执行。例如,先获取用户 ID,再根据 ID 获取用户的详细信息和订单列表。

在 Vue 3 的 <script setup> 语法中,我们可以利用 async/await 语法糖,让异步代码的书写和阅读体验如同同步代码一般流畅。

核心思路:
在一个 async 函数中,使用 await 关键字。await 会暂停函数的执行,直到其后的 Promise 对象完成(resolve 或 reject),然后恢复执行并返回 Promise 的结果。

代码示例:

javascript 复制代码
// composables/useUserFlow.js
import { ref } from 'vue'

export function useUserFlow() {
  const userData = ref(null)
  const ordersData = ref(null)
  const statsData = ref(null)
  const loading = ref(false)
  const error = ref(null)

  const handleUserFlow = async () => {
    loading.value = true
    error.value = null
    try {
      // 1. 等待第一个请求完成
      const userResponse = await fetch('/api/user')
      if (!userResponse.ok) throw new Error('获取用户失败')
      userData.value = await userResponse.json()
      console.log('1. 用户数据获取成功:', userData.value)

      // 2. 使用上一步的结果作为参数,等待第二个请求完成
      const ordersResponse = await fetch(`/api/orders?userId=${userData.value.id}`)
      if (!ordersResponse.ok) throw new Error('获取订单失败')
      ordersData.value = await ordersResponse.json()
      console.log('2. 订单数据获取成功:', ordersData.value)

      // 3. 等待第三个请求完成
      const statsResponse = await fetch('/api/stats')
      if (!statsResponse.ok) throw new Error('获取统计失败')
      statsData.value = await statsResponse.json()
      console.log('3. 统计数据获取成功:', statsData.value)

      // 4. 所有请求都按顺序完成后,执行后续处理
      console.log('所有请求完成,开始处理最终逻辑')
      // processFinalData(userData.value, ordersData.value, statsData.value)

    } catch (err) {
      // 任何一个请求失败,都会跳转到这里
      error.value = err.message
      console.error('请求流程中出现错误:', err)
    } finally {
      loading.value = false
    }
  }

  return {
    userData,
    ordersData,
    statsData,
    loading,
    error,
    handleUserFlow
  }
}

在组件中使用这个组合式函数:

vue 复制代码
<!-- UserProfile.vue -->
<script setup>
import { onMounted } from 'vue'
import { useUserFlow } from '@/composables/useUserFlow'

const { loading, error, handleUserFlow } = useUserFlow()

// 在组件挂载时触发串行请求
onMounted(() => {
  handleUserFlow()
})
</script>

<template>
  <div v-if="loading">加载中...</div>
  <div v-else-if="error">发生错误: {{ error }}</div>
  <div v-else>
    <!-- 渲染数据 -->
  </div>
</template>

🚀 场景二:并行执行,提升加载效率

当需要同时发起多个没有依赖关系的请求,并等待它们全部完成后再进行下一步时,使用 Promise.all 是最佳选择。这能显著提升效率,因为请求是同时发出的,总耗时约等于最慢的那个请求的时间,而不是所有请求耗时的总和。

Promise.all 接收一个 Promise 数组,并返回一个新的 Promise。只有当数组中所有的 Promise 都成功 resolve 时,它才会 resolve;只要有一个 Promise reject,它就会立即 reject。

代码示例:

javascript 复制代码
// composables/useDashboard.js
import { ref } from 'vue'

export function useDashboard() {
  const userInfo = ref(null)
  const productList = ref(null)
  const orderStats = ref(null)
  const loading = ref(false)
  const error = ref(null)

  const loadDashboardData = async () => {
    loading.value = true
    error.value = null
    try {
      // 同时发起三个独立的请求
      const [userRes, productRes, orderRes] = await Promise.all([
        fetch('/api/user/info'),
        fetch('/api/product/list'),
        fetch('/api/order/statistics')
      ])

      // 等待所有请求都成功后,统一处理结果
      userInfo.value = await userRes.json()
      productList.value = await productRes.json()
      orderStats.value = await orderRes.json()

      console.log('所有独立请求完成,开始处理仪表盘数据')
      // updateDashboard(userInfo.value, productList.value, orderStats.value)

    } catch (err) {
      // 只要有一个请求失败,Promise.all 就会立即失败并进入 catch
      error.value = err.message
      console.error('加载仪表盘数据失败:', err)
    } finally {
      loading.value = false
    }
  }

  return {
    userInfo,
    productList,
    orderStats,
    loading,
    error,
    loadDashboardData
  }
}

💡 进阶:处理部分失败的情况

Promise.all 的“一损俱损”特性在某些场景下可能不适用。例如,加载一个页面时,关键数据必须成功,但一些可选的推荐数据即使失败也不应影响页面的正常展示。这时,Promise.allSettled 就派上用场了。

Promise.allSettled 会等待所有传入的 Promise 完成(无论成功或失败),并返回一个数组,其中包含每个 Promise 的结果对象(包含 statusvaluereason)。

代码示例:

javascript 复制代码
async function robustDataLoad() {
  const promises = [
    fetch('/api/critical-data'), // 关键数据
    fetch('/api/optional-data-1'), // 可选数据
    fetch('/api/optional-data-2')  // 可选数据
  ]

  const results = await Promise.allSettled(promises)

  const criticalData = results[0].status === 'fulfilled' ? await results[0].value.json() : null
  const optionalData1 = results[1].status === 'fulfilled' ? await results[1].value.json() : null
  const optionalData2 = results[2].status === 'fulfilled' ? await results[2].value.json() : null

  // 即使可选数据加载失败,关键数据仍然可用
  if (criticalData) {
    renderPage(criticalData, optionalData1, optionalData2)
  } else {
    showError('关键数据加载失败')
  }
}

📌 总结与最佳实践

执行方式 核心语法 适用场景
串行执行 async/await 请求之间有强依赖关系,后一个请求需要前一个的结果。
并行执行 Promise.all 多个请求相互独立,需要等待所有请求都成功后再统一处理。
容错并行 Promise.allSettled 多个请求相互独立,即使部分失败也需要获取成功的结果。

最佳实践:

  1. 始终使用 try...catch:异步操作随时可能失败,使用 try...catch 块可以优雅地捕获和处理错误,避免应用崩溃。
  2. 管理加载状态:为用户提供清晰的反馈,例如在请求开始时显示加载指示器,请求结束后隐藏。
  3. 封装可复用的逻辑:将异步请求逻辑封装在组合式函数(Composables)中,可以提高代码的复用性和可维护性。
  4. 请求取消:在组件卸载时,考虑使用 AbortController 取消未完成的请求,防止内存泄漏和状态更新错误。