首页 > 其他分享 >一篇文章带你详细了解axios的封装

一篇文章带你详细了解axios的封装

时间:2023-06-05 09:33:35浏览次数:53  
标签:axios const 一篇 data error 封装 message config 请求

axios 封装

对请求的封装在实际项目中是十分必要的,它可以让我们统一处理 http 请求。比如做一些拦截,处理一些错误等。本篇文章将详细介绍如何封装 axios 请求,具体实现的功能如下

  • 基本配置

    配置默认请求地址,超时等

  • 请求拦截

    拦截 request 请求,处理一些发送请求之前做的处理,譬如给 header 加 token 等

  • 响应拦截

    统一处理后端返回的错误

  • 全局 loading

    为所有请求加上全局 loading(可配置是否启用)

  • 取消重复请求

当同样的请求还没返回结果再次请求直接取消

基础配置

这里以 vue3 为例,首先安装 axios,element-plus

npm i axios element-plus

在 src 下新建 http/request.ts 目录用于写我们的封装逻辑,然后调用 aixos 的 create 方法写一些基本配置

import axios, { AxiosResponse, InternalAxiosRequestConfig } from 'axios';
const service = axios.create({
  method: 'get',
  baseURL: import.meta.env.VITE_APP_API, //.env中的VITE_APP_API参数
  headers: {
    'Content-Type': 'application/json;charset=utf-8',
  },
  timeout: 10000, //超时时间
});

export default service;

这样便完成了 aixos 的基本配置,接下来我们可以在 http 下新建 api 目录用于存放我们接口请求,比如在 api 下创建 login.ts 用于写登录相关请求方法,这里的/auth/login我已经用 nestjs 写好了

import request from './request';
export const login = (data: any) => {
  return request({
    url: '/auth/login',
    data,
    method: 'post',
  });
};

然后可以在页面进行调用

<script lang="ts" setup>
import { login } from '@/http/login';
const loginManage = async () => {
  const data = await login({
    username: '鸡哥哥',
    password: '1234',
  });
  console.log(data);
};
loginManage();
</script>

结果打印如下

image.png

响应拦截器

我们可以看到返回的数据很多都是我们不需要的,我们需要的只有 data 中的数据,所以这时候我们便需要一个响应拦截器进行处理,同时在响应拦截器中我们不仅仅简单处理这个问题,还需要对后端返回的状态码进行判断,如果不是正确的状态码可以弹窗提示后端返回的描述(也可以自定义)

service.interceptors.response.use(
  (res: AxiosResponse<any, any>) => {
    const { data } = res;
    if (data.code != 200) {
      ElMessage({
        message: data.describe,
        type: 'error',
      });
      if (data.code === 401) {
        //登录状态已过期.处理路由重定向
        console.log('loginOut');
      }
      throw new Error(data.describe);
    }
    return data;
  },
  (error) => {
    let { message } = error;
    if (message == 'Network Error') {
      message = '后端接口连接异常';
    } else if (message.includes('timeout')) {
      message = '系统接口请求超时';
    } else if (message.includes('Request failed with status code')) {
      message = '系统接口' + message.substr(message.length - 3) + '异常';
    }
    ElMessage({
      message: message,
      type: 'error',
    });
    return Promise.reject(error);
  },
);

这里规定后台 code 不是 200 的请求是异常的,需要弹出异常信息(当然这里由自己规定),同时 401 状态表示登录已过期,如果你需要更多的异常处理都可以写在这里。注意这里都是对 code 状态码的判断,这表示后台返回的 http 的 status 都是 2xx 才会进入的逻辑判断,如果后台返回 status 异常状态码比如 4xx,3xx 等就会进入 error 里,可以在 error 里进行逻辑处理,这里要和后端小朋友约定好

请求拦截器

请求请求拦截器和响应拦截器类似,只不过是在请求发送之前我们需要做哪些处理,它的用法如下

service.interceptors.request.use(
  (config: InternalAxiosRequestConfig<any>) => {
    console.log(config);
    return config;
  },
  (error) => {
    console.log(error);
  },
);

image.png

我们可以看到 config 中包含了我们请求的一些信息像 headers,data 等等,我们是可以在这里对其进行修改的,比如我们在 headers 加一个 token

declare module "axios" {
  interface InternalAxiosRequestConfig<D = any, T = any> {
    isToken?: boolean;
  }
}
declare module "axios" {
  interface AxiosRequestConfig<D = any> {
    isToken?: boolean;
  }
}

service.interceptors.request.use(
  (config: InternalAxiosRequestConfig<any>) => {
    const { isToken = true } = config;
    if (localStorage.getItem('token') && !isToken) {
      config.headers['Authorization'] =
        'Bearer ' + localStorage.getItem('token'); // 让每个请求携带自定义token 请根据实际情况自行修改
    }
    return config;
  },
  (error) => {
    console.log(error);
  },
);

这里假设用户登录成功将 token 缓存到了 localStorage 中,接口是否需要 token 则是在请求的时候自己配置,比如 login 接口不加 token,注意这里需要给InternalAxiosRequestConfigAxiosRequestConfig加上自定义的字段,否则 TS 会报错

export const login = (data: any) => {
  return request({
    url: '/auth/login',
    data,
    isToken: false,
    method: 'post',
  });
};

image.png

此时我们可以获取到 config 中的 isToken 了

添加全局 loading

我们通常会在请求开始前加上 loading 弹窗,请求结束再进行关闭,实现其实很简单,在请求拦截器中调用 ElLoading.service 实例,响应拦截器中 close 即可。但是这样会出现一个问题,

当多个请求进入会开启多个 loading 实例吗? 这倒不会,因为在 element-plus 中的 ElLoading.service()是单例模式,只会开启一个 loading。

上述问题虽然不需要我们考虑,但是还有一个问题

同时进来多个请求,此时 loading 已经开启,假设 1 秒后多个请求中其中一个请求请求完成,按照上述逻辑会执行 close 方法,但是还有请求未完成 loading 却已经关闭,显然这不符合我们的期望

因此,我们可以定义一个变量用于记录正在请求的数量,当该变量为 1 时开启 loading,当变量为 0 时关闭 loading,同样的我们还定义了 config 中的 loading 让开发者自己决定是否开启 loading,实现如下

let requestCount = 0;
const showLoading = () => {
  requestCount++;
  if (requestCount === 1) loadingInstance();
};
const closeLoading = () => {
  requestCount--;
  if (requestCount === 0) loadingInstance().close();
};
service.interceptors.request.use(
  (config: InternalAxiosRequestConfig<any>) => {
    const { loading = true, isToken = true } = config;
    return config;
  },
  (error) => {
    console.log(error);
  },
);

service.interceptors.response.use(
  (res: AxiosResponse<any, any>) => {
    const { data, config } = res;
    const { loading = true } = config;
    if (loading) closeLoading();
  },
  (error) => {
    closeLoading();
    return Promise.reject(error);
  },
);

取消重复请求

当同样的请求还没返回结果再次请求我们需要直接取消这个请求,通常发生在用户连续点击然后请求接口的情况,但是如果加了 loading 这种情况就不会发生。axios 中取消请求可以使用AbortController,注意这个 api 需要 axios 版本大于 v0.22.0 才可使用,低版本可以使用CancelToken,下面看一下AbortController使用方法

service.interceptors.request.use(
  (config: InternalAxiosRequestConfig<any>) => {
    const controller = new AbortController();
    const { loading = true, isToken = true } = config;
    config.signal = controller.signal;
    controller.abort();

    return config;
  },
  (error) => {
    console.log(error);
  },
);

这里是将 controller 的 signal 赋值给 config 的 sigal,然后执行 controller 的 abort 函数即可取消请求

image.png

知道了如何取消 axios 请求,接下来我们就可以写取消重复请求的逻辑了

当拦截到请求的时候,将 config 中的 data,url 作为 key 值,AbortController 实例作为 value 存在一个 map 中,判断是否 key 值是否存在来决定是取消请求还是保存实例

const requestMap = new Map();
service.interceptors.request.use(
  (config: InternalAxiosRequestConfig<any>) => {
    const controller = new AbortController();
    const key = config.data + config.url;
    config.signal = controller.signal;
    if (requestMap.has(key)) {
      requestMap.get(key).abort();
      requestMap.delete(key);
    } else {
      requestMap.set(key, controller);
    }

    return config;
  },
  (error) => {
    console.log(error);
  },
);

我们短时间内发送两次请求就会发现有一个请求被取消了

到这里基本就完成了 axios 的封装,下面是完整代码,直接 CV,就可以摸鱼一整天~

import axios, {
  AxiosInstance,
  AxiosResponse,
  InternalAxiosRequestConfig,
} from "axios";
import { ElMessage, ElLoading } from "element-plus";
const loadingInstance = ElLoading.service;
let requestCount = 0;
const showLoading = () => {
  requestCount++;
  if (requestCount === 1) loadingInstance();
};
const closeLoading = () => {
  requestCount--;
  if (requestCount === 0) loadingInstance().close();
};

const service: AxiosInstance = axios.create({
  method: "get",
  baseURL: import.meta.env.VITE_APP_API,
  headers: {
    "Content-Type": "application/json;charset=utf-8",
  },

  timeout: 10000,
});
//请求拦截

declare module "axios" {
  interface InternalAxiosRequestConfig<D = any, T = any> {
    loading?: boolean;
    isToken?: boolean;
  }
}
declare module "axios" {
  interface AxiosRequestConfig<D = any> {
    loading?: boolean;
    isToken?: boolean;
  }
}

const requestMap = new Map();
service.interceptors.request.use(
  (config: InternalAxiosRequestConfig<any>) => {
    const controller = new AbortController();
    const key = config.data + config.url;
    config.signal = controller.signal;
    if (requestMap.has(key)) {
      requestMap.get(key).abort();
      requestMap.delete(key);
    } else {
      requestMap.set(key, controller);
    }
    console.log(123);

    const { loading = true, isToken = true } = config;

    if (loading) showLoading();
    if (localStorage.getItem("token") && !isToken) {
      config.headers["Authorization"] =
        "Bearer " + localStorage.getItem("token"); // 让每个请求携带自定义token 请根据实际情况自行修改
    }

    return config;
  },
  (error) => {
    console.log(error);
  }
);

service.interceptors.response.use(
  (res: AxiosResponse<any, any>) => {
    const { data, config } = res;

    const { loading = true } = config;
    if (loading) closeLoading();

    if (data.code != 200) {
      ElMessage({
        message: data.describe,
        type: "error",
      });
      if (data.code === 401) {
        //登录状态已过期.处理路由重定向
        console.log("loginOut");
      }
      throw new Error(data.describe);
    }
    return data;
  },
  (error) => {
    closeLoading();
    let { message } = error;
    if (message == "Network Error") {
      message = "后端接口连接异常";
    } else if (message.includes("timeout")) {
      message = "系统接口请求超时";
    } else if (message.includes("Request failed with status code")) {
      message = "系统接口" + message.substr(message.length - 3) + "异常";
    }
    ElMessage({
      message: message,
      type: "error",
    });
    return Promise.reject(error);
  }
);
export default service;

关注公众号web前端进阶每日更新最新前端技术文章,你想要的都有!

标签:axios,const,一篇,data,error,封装,message,config,请求
From: https://www.cnblogs.com/zdsdididi/p/17456991.html

相关文章

  • element-ui 组件二次封装
    习题链接element-ui组件二次封装课程列表修复bug,实现elementui组件中的单选功能。关键点作用域插槽(ScopedSlots)在父组件中使用slot-scope="scope"指定了一个名为scope的变量,当然也可以使用其他名称在插槽中就可以使用scope是点父组件中的数据ElementUI组件的Radi......
  • 【了不起的芯片 - 读书笔记】CPU 的制作流程 ( 晶圆制作 | 光刻机光刻流程 | 蚀刻过程
    文章目录一、晶圆制作二、光刻机光刻流程三、蚀刻过程四、涂层过程五、重复上述步骤若干次六、芯片封装一、晶圆制作晶圆制作是半导体芯片制造的关键过程,它涉及将硅晶片(或其他半导体材料)转化为可以用于集成电路制造的基础材料。下面是晶圆制作的主要步骤:单晶生长:通过化学气相沉......
  • 一篇搞定MySQL索引长度(key_len)计算规则
    MySQL索引长度(key_len)计算 计算规则索引字段:没有设置NOTNULL,则需要加1个字节。定长字段:tinyint占1个字节、int占4个字节、bitint占8个字节、date占3个字节、datetime占5 个字节、char(n)占n个字节。变长字段:varchar(n)占n个字符+2个字节。注......
  • java 封装
    1.面向对象思想为什么使用面向对象使人和计算机的交流更加流畅;提高开发效率生活中/计算机描述对象对比生活中的对象定义:看的见摸得着的都是对象计算机中的对象的定义:1.类2.属性3.方法类图使用类图描述类:用于分析和设计类;直观,容易理解; ......
  • 人生第一篇博客
    蹉跎了三十余载,从事开发也有十二年了,但是只有经验,技术一般。究其原因还是对技术浅尝辄止,只求会用,简单解决问题不求甚解,没有系统的学习过计算机底层的知识。今天在这里发个愿,坚持去学习一下计算机的底层知识:计算机组成原理、计算机操作系统、编译原理、数据结构和算法、计......
  • VB运行库+FlashPlayer国际去广告版安装脚本(支持静默安装封装部署时调用)
    前言:此脚本可静默安装VB运行库和flash国际版三合一程序,flash是去广告最新版,已测试不会有乱七八糟的弹窗广告,绿色无毒!!制作脚本的目的主要是为了离线封装系统时方便在部署阶段调用安装支持库。个人认为,离线封装系统是最绿色的,避免了二次封装破坏系统的稳定性,而且第三方封装工具都......
  • mock拦截axios请求,以及axios请求拦截设置token
     直接上源码:<!DOCTYPEhtml><html><head><metacharset="UTF-8"><title>mock拦截axios请求</title></head><body><!--本地npm安装mock和axios--><!--<scriptsrc="../node_modu......
  • Excel打印封装
    准备部分1、安装依赖npminstallvue-print-nb--save2、main.js全局安装importPrintfrom'vue-print-nb'Vue.use(Print);组件部分/components/ExcelForm.vue<template><div><divclass="btn"><el-buttonsize="......
  • pytest-mock mock的高层封装
    pytest-mockpytest-mock是一个pytest的插件,安装即可使用。它提供了一个名为mocker的fixture,仅在当前测试function或method生效,而不用自行包装。objectmock一个object,是最常见的需求。由于function也是一个object,以下以function举例。importosdefrm(filename):os.remove......
  • 简单封装一下pymysql库
      简单封装一下pymysql库my_pymysql.py#!/bin/python#-*-coding:utf-8-*-importpymysqlimportnumpydefget_connect(**kwargs):mysqldb=pymysql.connect(host=kwargs.get('host'),user=kwargs.get('user'),passw......