前端学习:Axios Http请求库入门与实战应用

news/2025/2/1 8:36:44 标签: http, 网络协议, 网络

什么是Promise?

Promise 是一个表示异步操作最终完成或失败的对象。它允许你更优雅地处理异步操作,避免回调地狱(Callback Hell)。

特点:

  • 异步性:Promise 代表一个异步操作的最终完成或失败。

  • 不可更改:一旦 Promise 的状态确定为成功(resolved)或失败(rejected),它就不会再改变。

  • 链式调用:可以将多个异步操作按顺序排列,形成链式调用。

Promise的基本状态

一个 Promise 对象有三种状态:

状态含义
Pending异步操作尚未完成(初始状态)。
Fulfilled异步操作成功完成。
Rejected异步操作失败。

创建一个Promise

Promise 通过其构造函数创建,并且需要传递一个执行器(executor)函数。executor 函数接收两个参数:resolvereject

// 创建一个 Promise
const promise = new Promise((resolve, reject) => {
  // 异步操作
  setTimeout(() => {
    const success = true; // 假设操作成功
    if (success) {
      resolve("操作成功!"); // 处理成功结果
    } else {
      reject("操作失败!"); // 处理失败结果
    }
  }, 1000);
});

参数解释:

resolve: 标志异步操作成功,将 Promise 状态变为 Fulfilled

reject: 标志异步操作失败,将 Promise 状态变为 Rejected

resolvereject 是构造器内部定义的函数,会在 Promise 被创建时自动传递给执行器。你的任务是通过执行器的逻辑调用这些函数,告诉 Promise 操作成功还是失败。

resolve() 是一个由 Promise 构造器提供的函数,用于标记异步操作成功完成的状态。它是 Promise 执行器(executor)的一部分。当你在执行器中调用 resolve(value) 时,你会将一个值传递给它,这个值会作为 Promise 的成功结果。

const promise = new Promise((resolve) => {
  // 模拟异步操作
  setTimeout(() => {
    const successMessage = "操作成功";
    resolve(successMessage); // 将 "操作成功" 传递出去
  }, 1000);
});

.then() 方法的第一个参数是一个回调函数,用于处理 resolve() 的结果。这个回调函数会接收到 resolve() 传入的参数。

promise
  .then((result) => {
    console.log(result); // 输出: 操作成功
  })
  .catch((error) => {
    console.error(error);
  });

 

resolve() 可以传递任何类型的值,包括:

  • 基本数据类型(字符串、数字、布尔值等)。

  • 对象或数组。

  • 甚至可以传递另一个 Promise。

同理,reject函数用于传递Error错误的详细参数信息。reject() 是 Promise 中用于传递错误信息的机制。它的主要作用是告诉 Promise,异步操作失败了,并且可以携带具体的错误信息(如错误的原因、错误的详细描述等)。

使用Promise

Promise 的主要方法是 .then().catch(),它们分别用于处理成功和失败的结果。

.then() 方法

promise
  .then(
    (result) => {
      console.log(result); // 输出:操作成功!
    },
    (error) => {
      console.error(error); // 如果失败,会执行这里
    }
  );

then 方法用于处理 executor 中的成功结果。它可以接收两个回调函数:

  1. 第一个参数:处理成功的回调函数。

  2. 第二个参数(可选):处理失败的回调函数。

.catch() 方法

catch 方法专门用于处理失败的结果。

promise
  .then((result) => {
    console.log(result); // 输出:操作成功!
  })
  .catch((error) => {
    console.error(error); // 如果失败,会执行这里
  });

Promise的链式调用

Promise 支持链式调用,这有助于将多个异步操作按顺序排列。

function fetchUser() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ id: 1, name: "John Doe" });
    }, 1000);
  });
}

function fetchUserDetails(userId) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ age: 30, email: "john@example.com" });
    }, 1000);
  });
}

// 链式调用
fetchUser()
  .then((user) => {
    console.log("用户信息:", user);
    return fetchUserDetails(user.id); // 返回一个新的 Promise
  })
  .then((details) => {
    console.log("用户详细信息:", details);
  })
  .catch((error) => {
    console.error("错误:", error);
  });

什么是 Axios?

Axios 是一个基于 Promise 的 HTTP 库,用于发送网络请求。它可以在浏览器和 Node.js 环境中使用,支持各种 HTTP 方法(如 GET, POST, PUT, DELETE 等)。它的主要特点包括:

  • 简单易用:API 设计直观,易于上手。

  • 支持 Promise:与现代 JavaScript 无缝集成。

  • 拦截器:可以在请求发送前和响应接收后自定义处理逻辑。

  • 取消请求:可以取消正在执行的请求。

  • 错误处理:提供统一的错误处理机制。

  • 支持请求和响应的序列化。

什么是 async/await

async/await 是 JavaScript 提供的用于处理异步操作的语法糖,它简化了异步代码的编写和阅读。它的本质是基于 Promise 的异步操作封装,使得异步代码可以像同步代码一样书写,避免了 .then() 链的嵌套问题。

核心特点:
  • async:修饰一个函数,表明该函数包含异步操作,会自动返回一个 Promise

  • await:用于等待一个 Promise 完成,只能在 async 函数内部使用。

// async/await
async function asyncFunction() {
  try {
    const result = await PromiseReturningFunction();
    return result;
  } catch (error) {
    console.error(error);
  }
}

// 等同于以下 Promise 写法
function promiseFunction() {
  return PromiseReturningFunction()
    .then(result => {
      return result;
    })
    .catch(error => {
      console.error(error);
    });
}
更方便链式调用和组合

async/await 允许将异步操作写成一个序列,而不用像 then() 那样通过链式调用来处理依赖关系。

// 使用 then()
fetchData1()
  .then(data1 => {
    return fetchData2(data1);
  })
  .then(data2 => {
    return fetchData3(data2);
  })
  .then(data3 => {
    console.log(data3);
  });

// 使用 async/await
async function handleData() {
  const data1 = await fetchData1();
  const data2 = await fetchData2(data1);
  const data3 = await fetchData3(data2);
  console.log(data3);
}

业务场景和函数类型建议使用 async/await 的情况 

需要处理多个异步操作

当你需要按顺序执行多个异步操作,并且每个操作的结果可能会影响下一个操作时,async/await 是最佳选择。

async function fetchData() {
  try {
    const user = await fetchUserById(1);
    const posts = await fetchPostsByUserId(user.id);
    console.log('User:', user);
    console.log('Posts:', posts);
  } catch (error) {
    console.error('Error:', error);
  }
}
需要分解异步流程的复杂逻辑

对于复杂的异步流程,async/await 可以将逻辑分解为多个函数,使代码更模块化。

async function downloadAndProcessFile(url) {
  const filePath = await downloadFile(url);
  const fileContent = await readFile(filePath);
  const processedData = processFile(fileContent);
  return processedData;
}

 

创建 Axios 实例

Axios项目结构

src/
├── utils/           # 工具模块
│   ├── axios.js     # 封装 Axios 实例
├── api/             # API 模块
│   ├── auth.js      # 用户认证 API
│   ├── user.js      # 用户相关 API
├── store/           # 状态管理
│   ├── auth.js      # 用户认证状态管理
│   ├── user.js      # 用户信息状态管理
├── components/      # UI 组件
├── App.vue          # 根组件

在前端项目中,Axios 相关的文件通常位于以下位置:

  1. 项目根目录

    • package.json:通过 npmyarn 添加 Axios 依赖后的配置文件。

    • node_modules:包含 Axios 的安装包。

  2. 工具模块

    • src/utils/axios.jssrc/utils/request.js:用于封装 Axios 实例,统一配置请求和响应拦截器。

  3. API 模块

    • src/api/auth.js:封装用户认证相关的 API。

    • src/api/user.js:封装用户相关的 API(如获取用户信息)。

    • src/api/service.js:封装服务相关的 API(如获取服务列表)。

  4. Vuex 或 Pinia 模块

    • 如果项目使用状态管理库(如 Vuex 或 Pinia),API 调用逻辑可能会出现在这些模块中。

使用Vue3+Pinia项目进行演示

src/api/axios.js

// src/utils/axios.js (修改后)
import axios from 'axios';

const http = axios.create({
  baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000',
  timeout: 10000,
  headers: { 'Content-Type': 'application/json' }
});

// 请求拦截器优化
http.interceptors.request.use(config => {
  const accessToken = localStorage.getItem('accessToken');
  const refreshToken = localStorage.getItem('refreshToken');

  // 设置 accessToken 到请求头
  if (accessToken) {
    config.headers.Authorization = `Bearer ${accessToken}`;
  }

  // 如果是刷新 Token 的请求,将 refreshToken 放到自定义请求头
  if (config.url === '/auth/refresh-token' && refreshToken) {
    config.headers['X-Refresh-Token'] = refreshToken;
  }

  return config;
});

// 响应拦截器优化
http.interceptors.response.use(
  response => response.data, // 自动解构 data
  async error => {
    const originalRequest = error.config;

    // 检查是否为 401 错误且未重试过
    if (error.response?.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true; // 标记请求已重试

      try {
        // 使用独立实例避免拦截器循环
        const refreshClient = axios.create({
          baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000',
          headers: {
            'X-Refresh-Token': localStorage.getItem('refreshToken') // 通过自定义请求头传递 refreshToken
          }
        });

        // 调用刷新 Token 接口
        const { data } = await refreshClient.post('/auth/refresh-token');

        // 更新本地存储的 Token
        localStorage.setItem('accessToken', data.accessToken);
        localStorage.setItem('refreshToken', data.refreshToken);

        // 更新原始请求的 Authorization 头
        originalRequest.headers.Authorization = `Bearer ${data.accessToken}`;

        // 重新发起原始请求
        return http(originalRequest);
      } catch (refreshError) {
        // 刷新 Token 失败,清除本地存储并跳转到登录页
        localStorage.clear();
        window.location.href = '/login'; // 使用路由跳转而非页面刷新
        return Promise.reject(refreshError);
      }
    }

    // 其他错误直接抛出
    return Promise.reject(error);
  }
);

export default http;

接口服务层封装

// src/api/auth.js (保持良好)
import http from '@/utils/axios';

export const login = credentials => http.post('/auth/login', credentials);
export const register = userData => http.post('/auth/register', userData);
export const refreshToken = () => http.post('/auth/refresh-token'); // 参数通过拦截器自动携带

登录使用双 Token 策略保证安全性

双 Token 策略通常包括:

  • Access Token:短时间有效,用于用户认证。

  • Refresh Token:长时间有效,用于刷新 Access Token。

Pinia Store

// src/store/auth.js (组合式 API 版本)
import { defineStore } from 'pinia';
import { login, register } from '@/api/auth';
import { useRouter } from 'vue-router';

export const useAuthStore = defineStore('auth', () => {
  const router = useRouter();
  
  // State
  const isAuthenticated = ref(!!localStorage.getItem('accessToken'));
  const accessToken = ref(localStorage.getItem('accessToken') || null);
  const refreshToken = ref(localStorage.getItem('refreshToken') || null);

  // Actions
  const handleTokenUpdate = (newAccessToken, newRefreshToken) => {
    accessToken.value = newAccessToken;
    refreshToken.value = newRefreshToken;
    localStorage.setItem('accessToken', newAccessToken);
    localStorage.setItem('refreshToken', newRefreshToken);
    isAuthenticated.value = true;
  };

  const loginAction = async (credentials) => {
    try {
      const data = await login(credentials);
      handleTokenUpdate(data.accessToken, data.refreshToken);
    } catch (error) {
      throw new Error('登录失败: ' + error.message); // 抛出错误供组件处理
    }
  };

  const registerAction = async (userData) => {
    try {
      await register(userData);
      await router.push('/login'); // 正确使用路由
    } catch (error) {
      throw new Error('注册失败: ' + error.message);
    }
  };

  const logout = () => {
    accessToken.value = null;
    refreshToken.value = null;
    isAuthenticated.value = false;
    localStorage.clear();
    router.push('/login');
  };

  return {
    isAuthenticated,
    accessToken,
    refreshToken,
    login: loginAction,
    register: registerAction,
    logout
  };
});

组件中调用

<!-- Login.vue -->
<template>
    <!-- 保持原有模板内容,v-model 绑定保持不变 -->
</template>

<script setup>
import { ref } from 'vue'
import { useAuthStore } from '@/store/auth'

const username = ref('')
const password = ref('')

const handleLogin = () => {
  const authStore = useAuthStore()
  authStore.login({ 
    username: username.value, 
    password: password.value 
  })
}
</script>
<!-- Register.vue -->
<template>
    <!-- 保持原有模板内容,v-model 绑定保持不变 -->
</template>

<script setup>
import { ref } from 'vue'
import { useAuthStore } from '@/store/auth'

const username = ref('')
const email = ref('')
const password = ref('')

const handleRegister = () => {
  const authStore = useAuthStore()
  authStore.register({
    username: username.value,
    email: email.value,
    password: password.value
  })
}
</script>


http://www.niftyadmin.cn/n/5839207.html

相关文章

对比DeepSeek、ChatGPT和Kimi的学术写作撰写引言能力

引言 引言部分引入研究主题&#xff0c;明确研究背景、问题陈述&#xff0c;并提出研究的目的和重要性&#xff0c;最后&#xff0c;概述研究方法和论文结构。 下面我们使用DeepSeek、ChatGPT4以及Kimi辅助引言撰写。 提示词&#xff1a; 你现在是一名[计算机理论专家]&#…

Node.js——body-parser、防盗链、路由模块化、express-generator应用生成器

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1f4c3;个人状态&#xff1a; 研发工程师&#xff0c;现效力于中国工业软件事业 &#x1f680;人生格言&#xff1a; 积跬步…

一起学SysML v2规范(01)

1 00:00:01,560 --> 00:00:05,840 今天我们开始一个新的系列 2 00:00:06,690 --> 00:00:08,190 一起学SysML v2 3 00:00:08,200 --> 00:00:09,570 规范 4 00:00:15,770 --> 00:00:17,040 这里说一起学 5 00:00:17,050 --> 00:00:18,920 就是说我和大家一起学…

leetcode——将有序数组转化为二叉搜索树(java)

给你一个整数数组 nums &#xff0c;其中元素已经按 升序 排列&#xff0c;请你将其转换为一棵 平衡 二叉搜索树。 示例 1&#xff1a; 输入&#xff1a;nums [-10,-3,0,5,9] 输出&#xff1a;[0,-3,9,-10,null,5] 解释&#xff1a;[0,-10,5,null,-3,null,9] 也将被视为正确答…

基于遗传优化GRNN和Hog特征提取的交通标志识别算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1 HOG 4.2 GRNN&#xff08;General Regression Neural Network&#xff09;模型原理 4.3 遗传算法&#xff08;GA&#xff09;优化GRNN平滑因子 5.算法完整程序工程 1.算法运行效果图预…

3-Redis哨兵高可用集群搭建

本文介绍redis哨兵集群的搭建。redis sentinel是redis高可用主从集群的解决方案。通过sentinel在主从集群上&#xff0c;当master下线后&#xff0c;sentinel自动选择一个slave&#xff0c;将slave变成master&#xff0c;从而达到故障转移的目的&#xff0c;实现主从集群的高可…

【llm对话系统】大模型 Llama 源码分析之并行训练方案

1. 引言 训练大型语言模型 (LLM) 需要巨大的计算资源和内存。为了高效地训练这些模型&#xff0c;我们需要采用各种并行策略&#xff0c;将计算和数据分布到多个 GPU 或设备上。Llama 作为当前最流行的开源大模型之一&#xff0c;其训练代码中采用了多种并行技术。本文将深入 …

FreeRTOS从入门到精通 第十六章(任务通知)

参考教程&#xff1a;【正点原子】手把手教你学FreeRTOS实时系统_哔哩哔哩_bilibili 一、任务通知简介 1、概述 &#xff08;1&#xff09;任务通知顾名思义是用来通知任务的&#xff0c;任务控制块中的结构体成员变量ulNotifiedValue就是这个通知值。 &#xff08;2&#…