Vue3异步请求的最佳实践:async/await 与 Promise.then(),以及TypeScript中的泛型 T 和任意类型 any ,Vue3请求API接口数据,TS中的 T 和 any

前言

在 Vue3 开发中,异步请求是非常常见的操作,特别是在与后端 API 交互时。虽然 Vue.js 自带了一些对异步数据的处理方式,但合理使用异步操作是确保应用顺畅运行的关键。这里我分享一下 Vue3 异步请求的最佳实践,并探讨 async/await 和 Promise.then() 两种处理异步操作的方式,简单记录一下

一. 接口请求

这里,我们使用一个请求接口 callAskRecordList,它会从 /gpt/catalog 获取数据。这个接口封装了一个基于 get 的异步请求,同时为请求头添加了 Authorization 令牌,确保用户身份验证。请看

import { useUserStore } from '@/store' // 引入用户存储,用于获取用户信息
import { get } from "@/utils/request/get"; // 引入封装好的 GET 请求方法

// 查询参数方法,用泛型 <T> 以支持灵活的数据类型
export function callAskRecordList<T>() {
  const userStore = useUserStore(); // 获取用户存储中的数据
  return get<T>({
    // 设置请求的 URL 地址
    url: '/gpt/catalog', 
    // 设置请求头,包含 Bearer 令牌进行身份验证
    headers: { Authorization: `Bearer ` + userStore.userInfo.token }, 
  });
}




// 去掉注释
import { useAuthStore, useSettingStore, useUserStore } from '@/store'
import { get } from "@/utils/request/get";
export function callAskRecordList<T>() {
  const userStore = useUserStore();
  return get<T>({
    url: '/gpt/catalog',
    headers: { Authorization: `Bearer ` + userStore.userInfo.token },
  })
}


二. 使用 Vue

这里我们在 Vue 组件中直接使用这个接口。我们在 onMounted 生命周期钩子中触发异步请求,获取数据并更新组件的状态。在这个例子中,我们定义了一个 leftList,用于存储从后端获取的数据。请看

// 引入 Vue 中的 ref 进行响应式数据声明,引入 onMounted 钩子
import { ref, onMounted } from "vue"; 
// 引入我们定义的接口方法
import { callAskRecordList } from "@/api/userAsk.js"; 

// 定义一个响应式数据 leftList,用来存储从接口获取的数据
const leftList = ref<any[]>([]);

// 定义异步函数,用于调用接口并更新数据
async function callSideList() {
  try {
    // 使用 await 等待异步请求结果,确保 res 是 Promise 的结果
    const res = await callAskRecordList();
    if (res && res.data) { // 判断返回结果是否存在,以及是否包含数据
      leftList.value = res.data; // 如果数据存在,将其赋值给 leftList
    }
  } catch (error) {
    // 如果请求过程中出现错误,捕获并输出到控制台
    console.error('Request error:', error);
  }
}

// 页面挂载时触发,调用上面定义的 callSideList 函数获取数据
onMounted(() => {
  callSideList(); // 页面加载时调用异步函数
});




// 去掉注释
import { computed, ref, watch, onMounted } from "vue";
import { callAskRecordList1 } from "@/api/userAsk.js";
const leftList = ref<any[]>([]);
async function callSideList() {
  try {
    const res = await callAskRecordList();
    if (res && res.data) {
      leftList.value = res.data;
    }
  } catch (error) {
    console.error('Request error:', error);
  }
}
onMounted(() => {
  callSideList();
})



三. async 和 .then() 

为什么不建议在 async 函数中使用 .then()?有些同学习惯于在 async 函数中继续使用 .then() 语法,但这种做法可能会带来一些隐蔽的问题。比如,下面这种写法(不建议这样写):

async function callSideList() {
  try {
    // 使用 await 等待 Promise,但由于 .then() 没有返回,结果始终是 undefined
    await callAskRecordList().then(res => { 
      if (res && res.data) { // 判断返回的 res 是否存在及其数据
        leftList.value = res.data; // 将获取到的数据赋值给 leftList
      }
    });
  } catch (error) {
    console.error('Request error:', error); // 捕获错误
  }
}




// 去掉注释
async function callSideList() {
  try {
    await callAskRecordList().then(res => {
      if (res && res.data) {
        leftList.value = res.data;
      }
    });
  } catch (error) {
    console.error('Request error:', error);
  }
}

这样写的主要问题是 await 等待的实际上是 .then() 的返回值,而 .then() 没有显式返回一个值,默认返回 undefined。这种情况下,await 后的表达式始终是 undefined,容易导致代码逻辑出错。

更好的方式是(正确的做法是)直接使用 await:

async function callSideList() {
  try {
    // 使用 await 直接等待 callAskRecordList 的结果,确保逻辑清晰
    const res = await callAskRecordList();
    if (res && res.data) { // 判断返回的 res 是否包含数据
      leftList.value = res.data; // 将数据赋值给 leftList
    }
  } catch (error) {
    console.error('Request error:', error); // 捕获错误
  }
}




// 去掉注释
async function callSideList() {
  try {
    const res = await callAskRecordList();
    if (res && res.data) {
      leftList.value = res.data;
    }
  } catch (error) {
    console.error('Request error:', error);
  }
}

这样做的好处是:

  • 代码更加简洁直观。
  • 确保 await 确实等待的是请求的返回结果。
  • 避免由于 .then() 返回 undefined 导致的潜在错误。


四. 链式调用

传统的 .then().catch() 链式调用。如果你不想使用 async/await,而是选择使用传统的链式调用处理异步操作,也可以像下面这样写,请看

function callSideList() {
  callAskRecordList()
    .then((res) => { // 成功请求后处理结果
      if (res && res.data) { // 判断 res 和其中的数据是否存在
        leftList.value = res.data; // 将获取到的数据赋值给 leftList
      }
    })
    .catch((error) => { // 捕获并处理请求错误
      console.error('Request error:', error);
    });
}




// 去掉注释
function callSideList() {
  callAskRecordList()
    .then((res) => {
      if (res && res.data) {
        leftList.value = res.data;
      }
    })
    .catch((error) => {
      console.error('Request error:', error);
    });
}

这种写法同样是可行的,只不过代码的可读性可能不如 async/await 版本好,尤其是在处理复杂异步逻辑时,.then() 链式调用可能会导致所谓的“回调地狱”,使得代码结构变得混乱。


五. 状态码

当我们调用一个 API 时,服务器会返回一个 HTTP 响应,其中包含了标准的 HTTP 状态码(如 200 表示成功,400 表示客户端错误等)。此外,响应体中可能还包含一个后端自定义的状态码(如 res.data.code),用于表示业务逻辑层面的成功或失败。正确处理这两种状态码至关重要。如何处理,请看

async function handleDelete(item: string) {
  try {
    const res = await callMasterDel(item?.uuid);
    console.log(res);
    // 首先检查 HTTP 状态码
    if (res.status === 200) {

      // 再检查自定义的状态码
      if (res?.data?.code === 200) {
        ms.success(t('common.deleteSuccess'));
        // 其它逻辑
      } else {
        // 处理自定义状态码不是 200 的情况
        console.error('Custom status code is not 200:', res?.data?.code);
        ms.error(t('common.failed'));
      }

    } else {
      // 处理 HTTP 状态码不是 200 的情况
      console.error('HTTP status code is not 200:', res.status);
      ms.error(t('common.failed'));
    }
  } catch (error) {
    console.error('Request error:', error);
    ms.error(t('common.failed'));
  }
}

这里是一个异步删除操作的处理逻辑。通过 async/await 处理异步操作,并使用 try-catch 捕获错误。在处理服务器响应时,先检查 HTTP 状态码,再判断自定义的状态码。根据不同情况,分别执行成功和失败的逻辑,展示提示信息。代码清晰且具备良好的错误处理机制。

当前逻辑包含多个判断条件(HTTP 状态码、自定义状态码),并且每个条件下的分支可能有多行操作(日志、提示、错误处理)。不建议使用三元表达式,逻辑的嵌套层次较深,会增加代码的复杂性和可读性难度。使用 if-else 可以更直观地表达这些逻辑,减少混淆。

上面代码有提到的res?.data?,其中 .? 是可选链操作符。更多详细,请看

JavaScript 可选链操作符icon-default.png?t=O83Ahttps://blog.csdn.net/weixin_65793170/article/details/142360574?spm=1001.2014.3001.5501


六. 总结

        在 Vue.JS 中处理异步请求时,async/awaitPromise.then() 各有优劣。对于简单的异步操作,链式调用.then().catch()可以很好地解决问题,但在更复杂的异步场景下,async/await 提供了更加直观、简洁和易于维护的写法。

        此外,在使用 async/await 时,确保不要在 await 后面再接 .then(),避免在 async 函数中同时使用 .then()await,这样会导致返回 undefined,可能带来不可预见的错误。遵循这些最佳实践,将有助于提升代码的可读性和健壮性,也避免了一些不必要的潜在错误。


七. T 和 any

泛型 T 和 任意类型 any。上文中有使用到泛型 T ,这里来介绍一下泛型 T 和任意类型 any 的使用,请看

1. T(泛型): 泛型是类型参数,用于创建可重用的函数、接口或类。在定义时并不指定具体类型,而是由调用者传入的实际类型来确定。泛型的目的是使代码更加灵活和可复用,同时保持类型安全。

例如:
function identity<T>(arg: T): T {
  return arg;
}

let num = identity<number>(5);  // T 被推断为 number
let str = identity<string>("hello");  // T 被推断为 string

在这个例子中,identity 函数可以接受任意类型的参数,但在使用时类型是明确的,并且返回值会保留参数的类型。其中,<T>(arg: T): T 语法含义

  • <T>:泛型声明
  • (arg: T):参数类型
  • : T:返回类型

2. any any 是 TypeScript 中的一种类型,它表示任意类型。使用 any 时,TypeScript 不会对该变量进行类型检查,任何类型的值都可以赋给它,也可以从它那里获取任意类型的值。

例如:
function identityAny(arg: any): any {
  return arg;
}

let num = identityAny(5);  // 返回值类型是 any
let str = identityAny("hello");  // 返回值类型也是 any

这里,identityAny 函数可以接受任意类型的参数,但返回值类型也不确定,TypeScript 不会对此进行类型检查。

创作不易,感觉有用,就一键三连,感谢(●'◡'●)

猜你喜欢

转载自blog.csdn.net/weixin_65793170/article/details/142054909