ts封装axios

176 阅读4分钟

定义拦截器类型

type.ts

import { AxiosRequestConfig, AxiosResponse } from "axios"

//实例拦截器
export interface RequestInterceptors {
    //请求拦截
    requestInterceptors?: (config: AxiosRequestConfig) => AxiosRequestConfig
    requestInterceptorsCatch?: (err: any) => any
    //响应拦截
    responseInterceptors?: <T = AxiosResponse>(config: T) => T
    responseInterceptorsCatch?: (err: any) => any
}

//自定义传入的参数
export interface RequestConfig extends AxiosRequestConfig {
    interceptors?: RequestInterceptors
}

定义模块

module.ts

import { RequestConfig } from "./types"

export interface useRequestConfig<T> extends RequestConfig {
    data?: T
}

export interface useResponse<T> {
    code: number
    message: string
    data: T
}

export interface Req {
    format: string
}
export interface Res {
    code: string
    content: string
    msg: string
}

封装请求类

request.ts

import axios, { AxiosResponse } from 'axios'
import type { AxiosInstance, AxiosRequestConfig } from 'axios'
import type {RequestInterceptors,RequestConfig} from './types'

//请求类
class Request {
    //axios实例
    instance: AxiosInstance
    //拦截器对象
    interceptorsObj?: RequestInterceptors

    constructor(config: RequestConfig) {
        //创建实例
        this.instance = axios.create(config)
        this.interceptorsObj = config.interceptors

        //类请求拦截器
        this.instance.interceptors.request.use((req: AxiosRequestConfig) => {
            return req
        }, (err: any) => err)

        //使用实例请求拦截器
        this.instance.interceptors.request.use(
            this.interceptorsObj?.requestInterceptors,
            this.interceptorsObj?.requestInterceptorsCatch
        )
        //使用实例响应拦截器
        this.instance.interceptors.response.use(
            this.interceptorsObj?.responseInterceptors,
            this.interceptorsObj?.responseInterceptorsCatch
        )

        //类响应拦截器
        this.instance.interceptors.response.use((res: AxiosResponse) => {
            return res.data
        }, (err: any) => err)
    }



    request<T>(config: RequestConfig): Promise<T> {
        return new Promise((resolve, reject) => {
            if (config.interceptors?.requestInterceptors) {
                config = config.interceptors.requestInterceptors(config)
            }
            this.instance
                .request<any, T>(config)
                .then(res => {
                    // 如果我们为单个响应设置拦截器,这里使用单个响应的拦截器
                    if (config.interceptors?.responseInterceptors) {
                        res = config.interceptors.responseInterceptors<T>(res)
                    }
                    resolve(res)
                })
                .catch((err: any) => {
                    reject(err)
                })
        })
    }
}

export default Request

实例化请求类

index.ts

import Request from "./request";
import { useRequestConfig,useResponse } from "./module";

//实例化
const req = new Request({
    baseURL: process.env.VUE_APP_BASE_URL,
    timeout: 1000 * 60 * 5,
    interceptors: {
      // 请求拦截器
      requestInterceptors: config => {
        return config
      },
      // 响应拦截器
      responseInterceptors: result => {
        return result
      },
    },
  })


const request = <D,T=any>(config:useRequestConfig<D>)=>{
    const {method = 'GET'} = config
    if(method === 'get' || method === 'GET'){
        config.params = config.data
    }
    return req.request<useResponse<T>>(config)
}

export default request

注意process.env.VUE_APP_BASE_URL配置在 .env 文件中 VUE_APP_BASE_URL = "api.uomg.com"

封装接口调用

api/index.ts

import request from "@/utils/request"
import { Req, Res } from "@/utils/request/module"

export const getData = (data: Req) => {
    return request<Req, Res>({
        url: '/api/rand.qinghua',
        method: 'GET',
        data,
        interceptors: {
            requestInterceptors(res) {
                return res
            },
            responseInterceptors(result) {
                return result
            }
        }
    })
}

页面调用

xxx.vue

<script lang="ts" setup>
import {onMounted} from 'vue'
import {getData} from './../api'

onMounted(async ()=>{
  const res = await getData({format:'json'})
  console.log(res);
})

</script>

取消请求

我们需要将所有请求的取消方法保存到一个集合(这里我用的数组,也可以使用Map)中,然后根据具体需要去调用这个集合中的某个取消请求方法。

首先定义两个集合,示例代码如下:

// index.ts
import type {
  RequestConfig,
  RequestInterceptors,
  CancelRequestSource,
} from './types'

class Request {
  /*
  存放取消方法的集合
  * 在创建请求后将取消请求方法 push 到该集合中
  * 封装一个方法,可以取消请求,传入 url: string|string[] 
  * 在请求之前判断同一URL是否存在,如果存在就取消请求
  */
  cancelRequestSourceList?: CancelRequestSource[]
  /*
  存放所有请求URL的集合
  * 请求之前需要将url push到该集合中
  * 请求完毕后将url从集合中删除
  * 添加在发送请求之前完成,删除在响应之后删除
  */
  requestUrlList?: string[]

  constructor(config: RequestConfig) {
    // 数据初始化
    this.requestUrlList = []
    this.cancelRequestSourceList = []
  }
}

这里用的CancelRequestSource接口,我们去定义一下:

// type.ts
export interface CancelRequestSource {
  [index: string]: () => void
}

这里的key是不固定的,因为我们使用url做key,只有在使用的时候才知道url,所以这里使用这种语法。

取消请求方法的添加与删除

首先我们改造一下request()方法,它需要完成两个工作,一个就是在请求之前将url和取消请求方法push到我们前面定义的两个属性中,然后在请求完毕后(不管是失败还是成功)都将其进行删除,实现代码如下:

// index.ts
request<T>(config: RequestConfig): Promise<T> {
  return new Promise((resolve, reject) => {
    // 如果我们为单个请求设置拦截器,这里使用单个请求的拦截器
    if (config.interceptors?.requestInterceptors) {
      config = config.interceptors.requestInterceptors(config)
    }
    const url = config.url
    // url存在保存取消请求方法和当前请求url
    if (url) {
      this.requestUrlList?.push(url)
      config.cancelToken = new axios.CancelToken(c => {
        this.cancelRequestSourceList?.push({
          [url]: c,
        })
      })
    }
    this.instance
      .request<any, T>(config)
      .then(res => {
        // 如果我们为单个响应设置拦截器,这里使用单个响应的拦截器
        if (config.interceptors?.responseInterceptors) {
          res = config.interceptors.responseInterceptors<T>(res)
        }

        resolve(res)
      })
      .catch((err: any) => {
        reject(err)
      })
      .finally(() => {
        url && this.delUrl(url)
      })
  })
}

这里我们将删除操作进行了抽离,将其封装为一个私有方法,示例代码如下:

// index.ts
/**
 * @description: 获取指定 url 在 cancelRequestSourceList 中的索引
 * @param {string} url
 * @returns {number} 索引位置
 */
private getSourceIndex(url: string): number {
  return this.cancelRequestSourceList?.findIndex(
    (item: CancelRequestSource) => {
      return Object.keys(item)[0] === url
    },
  ) as number
}
/**
 * @description: 删除 requestUrlList 和 cancelRequestSourceList
 * @param {string} url
 * @returns {*}
 */
private delUrl(url: string) {
  const urlIndex = this.requestUrlList?.findIndex(u => u === url)
  const sourceIndex = this.getSourceIndex(url)
  // 删除url和cancel方法
  urlIndex !== -1 && this.requestUrlList?.splice(urlIndex as number, 1)
  sourceIndex !== -1 &&
    this.cancelRequestSourceList?.splice(sourceIndex as number, 1)
}

取消请求方法

现在我们就可以封装取消请求和取消全部请求了,我们先来封装一下取消全部请求吧,这个比较简单,只需要调用this.cancelRequestSourceList中的所有方法即可,实现代码如下:

// index.ts
// 取消全部请求
cancelAllRequest() {
  this.cancelRequestSourceList?.forEach(source => {
    const key = Object.keys(source)[0]
    source[key]()
  })
}

现在我们封装一下取消请求,因为它可以取消一个和多个,那它的参数就是url,或者包含多个URL的数组,然后根据传值的不同去执行不同的操作,实现代码如下:

// index.ts
// 取消请求
cancelRequest(url: string | string[]) {
  if (typeof url === 'string') {
    // 取消单个请求
    const sourceIndex = this.getSourceIndex(url)
    sourceIndex >= 0 && this.cancelRequestSourceList?.[sourceIndex][url]()
  } else {
    // 存在多个需要取消请求的地址
    url.forEach(u => {
      const sourceIndex = this.getSourceIndex(u)
      sourceIndex >= 0 && this.cancelRequestSourceList?.[sourceIndex][u]()
    })
  }
}

测试取消请求

首先我们在.server/index.ts中对取消请求方法进行导出,实现代码如下:

// 取消请求
export const cancelRequest = (url: string | string[]) => {
  return request.cancelRequest(url)
}
// 取消全部请求
export const cancelAllRequest = () => {
  return request.cancelAllRequest()
}

然后我们在app.vue中对其进行引用,实现代码如下:

<template>
  <el-button
    @click="cancelRequest('/api/common/weather/get15DaysWeatherByArea')"
    >取消请求</el-button
  >
  <el-button @click="cancelAllRequest">取消全部请求</el-button>
  <router-view></router-view>
</template>
<script setup lang="ts">
import request, { cancelRequest, cancelAllRequest } from './service'
</script>