vue3 如何封装一个请求异步变成同步

这是一个非常重要且常见的需求。在 Vue 3 中,我们通常不会真正地将异步请求"变成"同步(因为这会阻塞浏览器),而是通过一些模式让代码具有“同步”的书写和阅读风格,从而避免“回调地狱”。

核心方法是使用 async/await 语法,并结合 Vue 3 的响应式 API 来管理状态。


核心思路

  1. 创建响应式状态:使用 ref 或 reactive 来存储请求的 数据(data)加载状态(loading) 和 错误信息(error)

  2. 封装异步函数:创建一个 async 函数,内部使用 try...catch 来处理请求。

  3. 使用 await:在函数内部 await 异步请求,这样后面的代码就会“等待”请求完成后再执行,实现了代码逻辑上的“同步”

  4. 暴露状态和函数:将响应式状态和封装的函数暴露给组件使用。


封装示例:一个通用的异步请求 Hook/Composable

我们将创建一个名为 useRequest 的组合式函数,这是 Vue 3 组合式 API 推荐的封装方式。

第一步:创建 useRequest 函数

javascript

// composables/useRequest.js (或 useRequest.ts)
import { ref } from 'vue'

export function useRequest(apiFn) {
  // 1. 创建响应式状态
  const data = ref(null)   // 存储请求成功的数据
  const loading = ref(false) // 加载状态
  const error = ref(null)  // 存储请求失败的错误信息

  // 2. 封装异步请求函数
  const execute = async (...params) => {
    loading.value = true
    error.value = null
    data.value = null // 每次请求前重置数据

    try {
      // 使用 await "等待"请求完成,代码在此处暂停,直到请求有结果
      const response = await apiFn(...params)
      data.value = response.data // 假设后端返回的数据在 response.data 中
      return response // 如果需要,可以返回完整的响应
    } catch (err) {
      error.value = err.message || '请求失败'
      throw err // 将错误继续抛出,以便在调用处可以捕获
    } finally {
      loading.value = false // 无论成功失败,最后都关闭 loading
    }
  }

  // 3. 返回状态和执行函数,供组件使用
  return {
    data,
    loading,
    error,
    execute
  }
}
第二步:封装具体的 API 请求

通常,我们会使用 axios 或 fetch 等库。这里以 axios 为例。

javascript

// api/userApi.js
import axios from 'axios'

// 封装获取用户信息的 API
export const getUserInfo = (userId) => {
  return axios.get(`/api/user/${userId}`)
}

// 封装提交表单的 API
export const postUserData = (userData) => {
  return axios.post('/api/user', userData)
}
第三步:在 Vue 组件中使用

现在我们可以在组件中像使用“同步”代码一样调用异步请求了。

vue

<!-- UserProfile.vue -->
<template>
  <div>
    <!-- 加载状态 -->
    <div v-if="loading">Loading...</div>
    
    <!-- 错误状态 -->
    <div v-else-if="error" class="error">{{ error }}</div>
    
    <!-- 成功状态 -->
    <div v-else-if="data">
      <h1>用户信息</h1>
      <p>姓名: {{ data.name }}</p>
      <p>邮箱: {{ data.email }}</p>
    </div>

    <button @click="fetchUser(123)" :disabled="loading">
      {{ loading ? '请求中...' : '获取用户信息' }}
    </button>
  </div>
</template>

<script setup>
import { useRequest } from '@/composables/useRequest'
import { getUserInfo } from '@/api/userApi'

// 使用 useRequest Hook,传入具体的 API 函数
const { data, loading, error, execute: fetchUser } = useRequest(getUserInfo)

// 你也可以在组件挂载时自动调用
// fetchUser(123) // 传入参数 123 作为 userId

// 或者响应某个事件,如按钮点击
const handleClick = async () => {
  try {
    // 这里的代码会“等待”请求完成,仿佛在同步执行
    await fetchUser(123)
    // 请求成功后才执行这里的代码
    console.log('用户数据获取成功:', data.value)
  } catch (err) {
    // 错误已经在 useRequest 中处理了,这里可以做一些额外操作
    console.error('在组件中捕获到错误:', err)
  }
}
</script>

更进一步的封装:直接请求并返回 Promise

如果你不需要管理 loading 和 error 状态,只是想简单地调用一个函数并等待结果,可以更简单地封装:

javascript

// utils/request.js
import axios from 'axios'

export const request = async (config) => {
  try {
    const response = await axios(config)
    return response.data // 直接返回数据
  } catch (error) {
    // 可以在这里统一处理错误,例如弹出通知
    console.error('请求错误:', error)
    throw error // 重新抛出错误,让调用方可以捕获
  }
}

在组件中使用:

vue

<script setup>
import { request } from '@/utils/request'
import { ref } from 'vue'

const userData = ref(null)

const fetchUser = async () => {
  try {
    // 代码像同步一样简洁
    userData.value = await request({
      method: 'get',
      url: '/api/user/123'
    })
    console.log('请求成功,数据已更新')
  } catch (error) {
    console.error('请求失败', error)
  }
}
</script>

总结

方法优点适用场景
useRequest Hook功能强大,自带状态管理(loading, error, data)需要 UI 响应请求状态的场景(如显示加载动画、错误提示)
简单 request 函数极其简洁,只需关注数据本身简单的请求,不需要管理加载和错误状态

关键记住:Vue 3 中并没有真正将异步变同步的魔法,我们是通过 async/await 语法让异步代码的书写和阅读顺序像同步一样直观,同时利用响应式系统来优雅地更新UI状态。这种模式极大地改善了开发体验和代码可维护性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

leijmdas

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值