基于TypeScript 封装 axios 实现多个域名和服务的请求

815 阅读18分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

前言

公司的接口是进行微服务部署的,然后不同的服务有的还在不同的域名下,这样使用的时候就有点不方便了。所以呢就相对于当前的进行了封装。

实现的功能:

  • 对不同的响应的状态进行处理
  • 根据传入的请求接口实现识别不同的服务和域名和相应的请求头
  • 对重复发送的请求进行处理保存在map里面,可以手动取消或者请求成功之后自动取消请求
  • 自定义业务请求的返回体的处理函数

对项目源码:Github 地址

对axios不熟悉的可以翻翻 axios 的文档:链接

基础的封装

首先我们先实现一个基础的版本, 使用类来实现,这样的话适用性更广一些,不同的请求可以new 一个不同的配置等

初步的定义一个request的函数,通过该函数我们发起请求,接受三个参数。

参数类型说明
urlURLInterface请求接口的封装,由请求路径和请求的方式组成
paramsany请求参数
configAxiosRequestConfig额外的配置

下面是实例的代码

// index.ts
import axios, { AxiosRequestConfig, Axios } from 'axios'
import qs from 'qs'
import { URLInterface } from './index.inter'

/** 初始化项目 */
export class BaseAxios {
  constructor () {
    this.instance = axios.create()
  }
  /** 新的请求实例 */
  private instance: Axios

  /**
   * 发送请求
   * @param {URLInterface} url 接口
   * @param {*} params 参数
   * @param {AxiosRequestConfig} config 其余配置
   * @returns {Promise<any>}
   */
  request = (url: URLInterface, params: any = {}, config: AxiosRequestConfig = {}) => {
    // 接口的配置 默认get请求
    const { path, type = 'get' } = url
    // 发送请求
    return this.instance.request({
      url: path,
      method: type,
      data: /(post|POST)/.test(type) ? params : undefined, // post请求方式
      params: /(get|GET)/.test(type) ? params : undefined, // get 请求方式
      paramsSerializer: (params) => qs.stringify(params, { arrayFormat: 'repeat' }),
      ...config
    })
  }
}

对应ts接口定义, 请求的配置多加一个 cancelRepeatRequest 参数用于之后对于单独的接口调用的时候,是否要进行重复请求的判断

// index.inter.ts
import { Method } from 'axios'

/** 请求接口定义 */
export interface URLInterface {
  /** 请求的路径 */
  path: string
  /** 请求的类型 */
  type?: Method
  [key: string]: any
}

请求的拦截

对相应的请求头做基本的处理,比如200 404 这些进行统一的一个处理

在类初始化的时候我们注册一个请求和响应的拦截器

// index.ts

import axios, { AxiosRequestConfig, Axios } from 'axios'
import qs from 'qs'
import { URLInterface } from './index.inter'

/** 响应错误的处理逻辑 */
const onRejected = (error: any) => {
}

/** 初始化项目 */
export class BaseAxios {
  constructor () {
    this.instance = axios.create()
    // 注册请求和响应的拦截器
    this.initInterceptors()
  }
  /** 新的请求实例 */
  private instance: Axios

  /** 初始化:注册拦截器 */
  private initInterceptors () {
    this.instance.interceptors.request.use((config) => config)
    this.instance.interceptors.response.use((response: any) => response, onRejected)
  }
  // ...其他代码
}

完善相应错误的处理函数(onRejected)

/** 响应错误的处理逻辑 */
const onRejected = ({ message, response }: any) => {
  const err = { code: response.status, message }
  if (response) {
    const { status, data }: AxiosResponse = response
    switch (status) {
      case 400: err.message = `${status} 请求参数有误`; break
      case 401: err.message = `${status} 当前请求需要用户验证`; break
      case 403: err.message = `${status} 服务器已经理解请求,但是拒绝执行它`; break
      case 404: err.message = `${status} 请求路径不存在`; break
      case 405: err.message = `${status} 请求行中指定的请求方法不能被用于请求相应的资源`; break
      case 500: err.message = `${status} 服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理`; break
      case 502: err.message = `${status} 网关错误!`; break
      case 503: err.message = `${status} 由于临时的服务器维护或者过载,服务器当前无法处理请求。`; break
      case 504: err.message = `${status} 响应超时`; break
      case 505: err.message = `${status}  HTTP版本不受支持!`; break
      default: err.message = `${status} ${data?.message || message}`; break
    }
  }
  if (message.includes('timeout')) {
    err.message = '网络请求超时!'
    err.code = 504
  }
  if (message.includes('Network')) {
    err.message = window.navigator.onLine ? '网络未连接' : '网络错误'
    err.code = -7
  }
  return Promise.reject(err)
}

取消请求

准备工作

新增一个存放请求CancelToken的Map

  // index.ts
 
  /** 存储每个请求的值 */
  private pendingMap = new Map()

增加对Map中cancelToken操作的函数:

  • getPendingKey(生成唯一key)
  • removePending(删除)
  • addPending(新增)

生成的唯一的我们根据请求的头中的请求路径、请求方式、请求参数 拼接而成

url, method, JSON.stringify(params), JSON.stringify(data)

// index.ts

  /**
   * 生成每个请求唯一的键
   * @param {*} config
   * @returns string
   */
  private getPendingKey = (config: any) => {
    let { url, method, params, data } = config
    if (typeof data === 'string') data = JSON.parse(data) // response里面返回的config.data是个字符串对象
    return [url, method, JSON.stringify(params), JSON.stringify(data)].join('&')
  }

  /**
   * 储存每个请求唯一值, 也就是cancel()方法, 用于取消请求
   * @param {*} config
   */
  private addPending = (config: any) => {
    const pendingKey = this.getPendingKey(config)
    config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => {
      if (!this.pendingMap.has(pendingKey)) this.pendingMap.set(pendingKey, cancel)
    })
  }

  /**
   * 删除重复的请求
   * @param {*} config
   */
  private removePending = (config: any) => {
    const pendingKey = this.getPendingKey(config)
    if (this.pendingMap.has(pendingKey)) {
      const cancelToken = this.pendingMap.get(pendingKey)
      cancelToken('取消请求:' + pendingKey)
      this.pendingMap.delete(pendingKey)
    }
  }

请求的key添加和删除

在拦截器中增加请求的唯一key的值和判断

  • 先判断Map中是否存在key,存在则取消之前的请求重新添加key,不存在则添加当前的请求的key
  • 请求成功之后,在响应的拦截器中删除对应的请求key
/** 初始化:注册拦截器 */
  private initInterceptors () {
    this.instance.interceptors.request.use((config) => {
      // 取消重复请求: 添加唯一的请求,存在 那么证明正在请求中,直接取消之前的请求 然后添加新的请求
      this.removePending(config)
      this.addPending(config)
      return config
    })
    this.instance.interceptors.response.use((response: any) => {
      // 请求成功之后删除当前的请求key
      this.removePending(response.config)
      return response
    }, (error) => {
      // 请求失败之后移除Map中的key
      error.config && this.removePending(error.config)
      return onRejected(error)
    })
  }

手动控制取消请求或配置取消

有的情况下我们需要连续的去请求接口。
或者说在一个封装的组件中会有请求发送但是这个组件被在同一个界面中多次的掉用,这个时候就需要取消这个取消请求的设置。当然封装组件的时候最好不要出现这种情况😂。

  • 通过配置控制是否需要取消
  • 单个请求参数中是否需要取消
  • 调用函数取消所有的请求

思路:配置取消请求的方式,在新建axios的时候传入配置,使整个实体类所发出的请求都不进行自动的去掉重复的;在单个发送请求的时候传入参数配置,使当前的这个配置不进行自动进行取消重复请求。

首先新增一个变量保存初始化的时候传入的参数,并且修改constructor(构造函数)。

// index.ts
//... 其他代码

export class BaseAxios {
  constructor (closeCancelRequest: boolean = false) {
    this.closeCancelRequest = closeCancelRequest // 默认是不关闭自动取消重复请求的
    this.instance = axios.create()
    // 注册请求和响应的拦截器
    this.initInterceptors()
  }
  /** 是否关闭取消重复 */
  private closeCancelRequest: boolean
  //... 其他代码
}

修改拦截器函数中的重复请求的key的添加判断

/** 初始化:注册拦截器 */
  private initInterceptors () {
    this.instance.interceptors.request.use((config) => {
      if (!this.closeCancelRequest) { // 当为false时正常的添加去除
        // 取消重复请求: 添加唯一的请求,存在 那么证明正在请求中,直接取消之前的请求 然后添加新的请求
        this.removePending(config)
        this.addPending(config)
      } else if (this.closeCancelRequest) { // 关闭的时候只添加成功或失败之后才移除key
        this.addPending(config) // 一直添加覆盖不做自动取消的处理
      }
      return config
    })
    this.instance.interceptors.response.use((response: any) => {
      // 请求成功之后删除当前的请求key
      this.removePending(response.config)
      return response
    }, (error) => {
      // 请求失败之后移除Map中的key
      error.config && this.removePending(error.config)
      return onRejected(error)
    })
  }

全局的完成,接下来修改请求的函数(request)

取消重复的请求的参数我们存放在第三个参数中,所以需要重新定义一下参数的类型 新建ts接口定义
在index.inter.ts 文件中新增RequestMethodProps接口并且继承 AxiosRequestConfig

// index.inter.ts

import { Method, AxiosRequestConfig } from 'axios'

/** 请求接口定义 */
export interface URLInterface {
  /** 请求的路径 */
  path: string
  /** 请求的类型 */
  type?: Method
  [key: string]: any
}

/** 函数的请求配置 */
export interface RequestMethodProps extends AxiosRequestConfig {
  /** 关闭取消重复请求: 默认false 不关闭 */
  closeCancelRequest?: boolean
  /** 其余的值 */
  [key: string]: any
}

修改index.ts 中request的参数类型同时拦截器函数, 我们在interceptors.request的拦截器中可以获取到发送请求的时候传递的参数配置,获取到closeCancelRequest 参数后进行修改,我们优先判断request发送请求的参数, 函数 > 全局的;

修改之后的代码如下

/** 初始化:注册拦截器 */
  private initInterceptors () {
    this.instance.interceptors.request.use((config) => {
      // 请求函数的 全局的
      const { closeCancelRequest, tokenKey }: RequestMethodProps = config
      // 函数是否传递参数
      const method: boolean = typeof closeCancelRequest === 'boolean' && !closeCancelRequest
      // 全局的参数
      const global: boolean = typeof closeCancelRequest === 'undefined' && !this.closeCancelRequest
      if (method || global) {
        // 取消重复请求: 添加唯一的请求,存在 那么证明正在请求中,直接取消之前的请求 然后添加新的请求
        this.removePending(config)
        this.addPending(config)
      } else if (this.closeCancelRequest || tokenKey) { // 关闭的时候只添加成功或失败之后才移除key
        this.addPending(config) // 一直添加覆盖不做自动取消的处理
      }
      return config
    })
    this.instance.interceptors.response.use((response: any) => {
      // 请求函数的 全局的
      const { closeCancelRequest, tokenKey }: RequestMethodProps = response.config
      // 函数是否传递参数
      const method: boolean = typeof closeCancelRequest === 'boolean' && !closeCancelRequest
      // 全局的参数
      const global: boolean = typeof closeCancelRequest === 'undefined' && !this.closeCancelRequest
      // 请求成功之后删除当前的请求key
      if (method || global || this.closeCancelRequest || tokenKey) {
        this.removePending(response.config)
      }
      return response
    }, (error) => {
      // 请求失败之后移除Map中的key
      if (error.config) {
        // 请求函数的 全局的
        const { closeCancelRequest, tokenKey }: RequestMethodProps = error.config
        // 函数是否传递参数
        const method: boolean = typeof closeCancelRequest === 'boolean' && !closeCancelRequest
        // 全局的参数
        const global: boolean = typeof closeCancelRequest === 'undefined' && !this.closeCancelRequest
        if (method || global || this.closeCancelRequest || tokenKey) {
          this.removePending(error.config)
        }
      }
      return onRejected(error)
    })
  }

因为代码存在大量的复用所以抽取出来两个函数

  • returnReuslt: 判断需要进行添加值的情况
  • responseRemoveCancelToken:请求失败或者完成之后移除对应的key
import axios, { Axios, AxiosResponse } from 'axios'
import qs from 'qs'
import { URLInterface, RequestMethodProps } from './index.inter'

//... 其他代码

/** 初始化项目 */
export class BaseAxios {
  constructor (closeCancelRequest: boolean = false) {
    this.closeCancelRequest = closeCancelRequest // 默认是不关闭自动取消重复请求
    this.instance = axios.create()
    // 注册请求和响应的拦截器
    this.initInterceptors()
  }
  /** 是否关闭取消重复 */
  private closeCancelRequest: boolean
  /** 新的请求实例 */
  private instance: Axios
  /** 存储每个请求的值 */
  private pendingMap = new Map()

  //... 其他代码
  
  /** 判断需要进行添加值的情况 */
  private returnReuslt (config:RequestMethodProps):boolean {
    const { closeCancelRequest }: RequestMethodProps = config
    // 函数是否传递参数
    const method: boolean = typeof closeCancelRequest === 'boolean' && !closeCancelRequest
    // 全局的参数
    const global: boolean = typeof closeCancelRequest === 'undefined' && !this.closeCancelRequest
    return (method || global)
  }

  /** 请求失败或者完成之后移除对应的key */
  private responseRemoveCancelToken (config:RequestMethodProps) {
    // 需要移除的key 的情况
    if (this.returnReuslt(config) || this.closeCancelRequest) {
      this.removePending(config)
    }
  }

  /** 初始化:注册拦截器 */
  private initInterceptors () {
    this.instance.interceptors.request.use((config:RequestMethodProps) => {
      if (this.returnReuslt(config)) {
        // 取消重复请求: 添加唯一的请求,存在 那么证明正在请求中,直接取消之前的请求 然后添加新的请求
        this.removePending(config)
        this.addPending(config)
      } else if (this.closeCancelRequest) { // 关闭的时候只添加成功或失败之后才移除key
        this.addPending(config) // 一直添加覆盖不做自动取消的处理
      }
      return config
    })
    this.instance.interceptors.response.use((response: any) => {
      // 请求成功之后删除当前的请求key
      this.responseRemoveCancelToken(response.config)
      return response
    }, (error) => {
      // 请求失败之后移除Map中的key
      if (error.config) this.responseRemoveCancelToken(error.config)
      return onRejected(error)
    })
  }

  /**
   * 发送请求
   * @param {URLInterface} url 接口
   * @param {*} params 参数
   * @param {RequestMethodProps} config 其余配置
   * @returns {Promise<any>}
   */
  request = (url: URLInterface, params: any = {}, config: RequestMethodProps = {}) => {
    // 接口的配置 默认get请求
    const { path, type = 'get' } = url
    // 发送请求
    return = this.instance.request({
      url: path,
      method: type,
      data: /(post|POST)/.test(type) ? params : undefined, // post请求方式
      params: /(get|GET)/.test(type) ? params : undefined, // get 请求方式
      paramsSerializer: (params) => qs.stringify(params, { arrayFormat: 'repeat' }),
      ...config
    })
  }
}

到这一步我们就能够在初始化的时候决定是否需要自动取消重复的请求 和 在发送请求的时候单个请求是否关闭这个功能

接下来增加一个函数(onCancelRequestAll)进行取消所有的请求

// index.ts
  /** 取消所有的请求 */
  onCancelRequestAll = () => {
    this.pendingMap.forEach((key: string) => {
      this.removePending(key)
    })
  }

当离开当前的界面的时候想要取消所有的接口那么可以调用这个函数,进行当前的这个实例的发出的请求的取消

取消指定的请求

考虑到有可能出现需要取消指定请求的需求

所以做一个也许用不到也许用得到的设计,这个可能会变成代码的冗余也可能实际能使用到

实现思路:发送请求的时候给当前请求传递一个自定义key 用来保存当前的请求的一个CancelToken当我们在需要取消这个请求的时候,如果请求还未完成则可以根据这个key 来进行取消,如果完成或者失败了,那就没法了

首先创建一个自动生成唯一key的函数,主要是用来存放当前CancelToken

// index.ts
  /** 生成自定义的key */
  public createMapKey = ():string => {
    // 为了保持唯一性 用时间戳和随机的字符组合生成。
    // 同时判断pendingMap 中是否存在了这个key 存在则重新生成一个
    const hexDigits = '0123456789abcdefghijklmnopqrstuvwxyz'
    let cusStr:string = ''
    for (let i = 0; i < 8; i++) {
      cusStr += hexDigits.substr(Math.floor(Math.random() * 0x10), 1)
    }
    const key: string = new Date().getTime().toString() + cusStr
    // 判断是否在pendingMap 中存在,存在的话,重新生成
    return this.pendingMap.has(key) ? this.createMapKey() : key
  }

修改request 的第三个参数配置,新增一个参数用来传递生成的自定义的key, 我们在保存CancelToken的时候可以用这个来做key

重新定义参数接口

/** 函数的请求配置 */
export interface RequestMethodProps extends AxiosRequestConfig {
  /** 关闭取消重复请求: 默认false 不关闭 */
  closeCancelRequest?: boolean
  /** 重复请求的标识的key */
  tokenKey?: string
  /** 其余的值 */
  [key: string]: any
}

修改拦截器函数(initInterceptors)和 returnReuslt函数中的判断,增加了tokenKey字段的判断

// index.ts

  //...其他代码

  /** 生成自定义的key */
  public createMapKey = (): string => {
    // 为了保持唯一性 用时间戳和随机的字符组合生成。
    // 同时判断pendingMap 中是否存在了这个key 存在则重新生成一个
    const hexDigits = '0123456789abcdefghijklmnopqrstuvwxyz'
    let cusStr: string = ''
    for (let i = 0; i < 8; i++) {
      cusStr += hexDigits.substr(Math.floor(Math.random() * 0x10), 1)
    }
    const key: string = new Date().getTime().toString() + cusStr
    // 判断是否在pendingMap 中存在,存在的话,重新生成
    return this.pendingMap.has(key) ? this.createMapKey() : key
  }

/** 判断需要进行添加值的情况 */
  private returnReuslt (config: RequestMethodProps): boolean {
    const { closeCancelRequest }: RequestMethodProps = config
    // 函数是否传递参数
    const method: boolean = typeof closeCancelRequest === 'boolean' && !closeCancelRequest
    // 全局的参数
    const global: boolean = typeof closeCancelRequest === 'undefined' && !this.closeCancelRequest
    return (method || global)
  }

  /** 请求失败或者完成之后移除对应的key */
  private responseRemoveCancelToken (config: RequestMethodProps) {
    // 需要移除的key 的情况
    if (this.returnReuslt(config) || this.closeCancelRequest || config.tokenKey) {
      this.removePending(config)
    }
  }

  /** 初始化:注册拦截器 */
  private initInterceptors () {
    this.instance.interceptors.request.use((config: RequestMethodProps) => {
      if (this.returnReuslt(config)) {
        // 取消重复请求: 添加唯一的请求,存在 那么证明正在请求中,直接取消之前的请求 然后添加新的请求
        this.removePending(config)
        this.addPending(config)
      } else if (this.closeCancelRequest || config.tokenKey) { // 关闭的时候只添加成功或失败之后才移除key
        this.addPending(config) // 一直添加覆盖不做自动取消的处理
      }
      return config
    })
    this.instance.interceptors.response.use((response: any) => {
      // 请求成功之后删除当前的请求key
      this.responseRemoveCancelToken(response.config)
      return response
    }, (error) => {
      // 请求失败之后移除Map中的key
      if (error.config) this.responseRemoveCancelToken(error.config)
      return onRejected(error)
    })
  }

   //...其他代码

新增一个取消指定请求的函数: onCancelRequest

// index.ts

//...其他代码
  /** 取消单独的请求 */
  onCancelRequest = (tokenKey: string) => {
    this.removePending({ tokenKey })
  }
//...其他代码

对取消请求的进行测试

测试的代码我用的之前搭建的一个框架: Github 地址

创建了三个函数进行测试

函数
requestURLMore取消重复的请求测试
requestURLFun发送的请求中关闭取消重复请求和单独的一起的进行测试
requestURL取消指定请求测试
// src/page/Cancel.tsx
import React from 'react'
import { Card, Space, Button } from 'antd'
import { allSettled } from '@util/index'
import type { AllSettledResponse } from '@util/index'
import { BaseAxios } from '@components/axios'
import { URLInterface } from '@components/axios/index.inter'

const url:URLInterface = { path: 'https://api.apishop.net/common/weather/get15DaysWeatherByArea' }
const axios = new BaseAxios()

export const CancelItem = () => {
  /** 同时发送取消重复的请求 */
  const requestURLMore = () => {
    // 两个请求会取消一个请求
    allSettled([
      axios.request(url, {}), // 发送第二个的时候被取消
      axios.request(url, {}), // 发送
    ]).then((responseAry:AllSettledResponse[]) => {
      responseAry.forEach((response:AllSettledResponse, index: number) => {
        if (response.status === 'success') {
          console.log('success:' + index, response.value)
        } else {
          console.log('failure:' + index, response.value)
        }
      })
    })
  }

  /** 函数指定是否关闭取消请求 */
  const requestURLFun = () => {
    // 发送了三个请求会取消一个请求
    const closeCancelRequest = true
    allSettled([
      axios.request(url, {}, { closeCancelRequest }), // 发送
      axios.request(url, {}, { closeCancelRequest }), // 发送
      axios.request(url, {}, { closeCancelRequest: false }), // 发送第四个的时候被取消
      axios.request(url, {}, { closeCancelRequest: false }), // 发送
    ]).then((responseAry:AllSettledResponse[]) => {
      responseAry.forEach((response:AllSettledResponse, index: number) => {
        if (response.status === 'success') {
          console.log('success:' + index, response.value)
        } else {
          console.log('failure:' + index, response.value)
        }
      })
    })
  }

  /** 取消指定请求 */
  const requestURL = () => {
    const tokenKey = axios.createMapKey()
    axios.request(url, {}, { tokenKey }).then((res) => {
      console.log('success:', res)
    }).catch((err) => {
      console.log('error:', JSON.stringify(err))
    })
    setTimeout(() => {
      axios.onCancelRequest(tokenKey)
    }, 200)
  }
  return <Card>
    <Space>
      <Button type='primary' onClick={requestURLMore}>取消重复的请求</Button>
      <Button type='primary' onClick={requestURLFun}>函数指定是否关闭取消请求</Button>
      <Button type='primary' onClick={requestURL}>取消指定的请求</Button>
    </Space>
  </Card>
}

测试结果:

requestURLMore: 同时发送两条请求;取消掉了上一条请求,发送了最后一条请求

image.png

requestURLFun: 发送四条请求,其中前两条请求正常发送,后两条进行去重复;前两条正常请求成功,第三条请求在发送第四天请求时,被取消掉,第四条发送成功

image.png

requestURL: 取消指定的请求;发送了一条请求,因为需要有延时的操作,所以对浏览器的Network进行了自定义流量和响应时间的设置,测试结果,可以取消掉该条请求

image.png

多个域名和服务的封装

请求域名请求头的判断处理

为了能够对不同的域名和服务进行请求,所以需要对传入的请求路径进行处理。

思路:在初始化axios 的时候,传递进入实体类中。实现需求,存在一个基础的域名,该域名为所有的基本域名,没有配置服务的都走基础的域名,设定一个数组存放域名,域名可以指定请求的服务,如果没有服务则走基础域名。

首先定义ts 接口

// index.inter.ts

/** axios的参数配置 */
export interface AxiosProps {
  /** 请求的域名等 */
  requestConfig?: RequestConfigProps
  /** 超时的时间 */
  timeout?: number
  /** 关闭取消重复请求: 默认false 不关闭 */
  closeCancelRequest?: boolean
}

/** 服务名 */
export interface ServiceConfigProps {
  /** 服务名称 */
  name: string
  /** 请求头 */
  header?: any
  /** 超时的时间 */
  timeout?: number
}

/** 域名数组 */
export interface DomainAryProps {
  /** 域名 */
  domainName: string
  /** 请求头 */
  headers?: any
  /** 超时的时间 */
  timeout?: number
  /** 服务名 */
  serviceName?: (string | ServiceConfigProps)[]
}

/** 请求配置 */
export interface RequestConfigProps {
  /** 基础的域名 */
  domainName: string
  /** 基础的请求头 */
  headers?: any
  /** 超时的时间 */
  timeout?: number
  /** 详细的配置 */
  domainAry?: DomainAryProps[]
}

修改axios构造函数(constructor),增加传递了配置参数requestConfig 和 timeout

axios中新增字段

字段说明
requestConfig请求域名的配置
timeout请求超时时间
// ...其他代码
  constructor ({ closeCancelRequest = false, requestConfig, timeout }:AxiosProps) {
    this.closeCancelRequest = closeCancelRequest // 默认是不关闭自动取消重复请求
    this.requestConfig = requestConfig // 请求的配置
    this.timeout = timeout // 请求超时时间
    this.instance = axios.create()
    // 注册请求和响应的拦截器
    this.initInterceptors()
  }

  /** 请求的配置 */
  private requestConfig?: RequestConfigProps
  /** 请求超时的时间 */
  private timeout?: number
  // ...其他代码
}

新增函数处理判断请求接口

/**
 * 根据请求路径返回基础域名和请求头等
 * @param {URLInterface}       url    请求接口
 * @param {RequestConfigProps} config 请求配置
 * @returns {HandleRequestResponse}
 */
const handleRequest = (url: URLInterface, config?: RequestConfigProps):HandleRequestResponse => {
  if (config) {
    const { domainAry, domainName, headers }:RequestConfigProps = config
    // 请求的域名
    let domain: string | undefined
    let header: any
    let timeout: number | undefined
    if (domainAry) { // 判断是否有域名数组,存在则进行查询看是否存在服务
      for (let index = 0; index < domainAry.length; index++) {
        const dm: DomainAryProps = domainAry[index] // 整个域名的
        if (!dm.serviceName) { // 不存在服务名称则跳过
          continue
        } else { // 存在则进行判断
          const service: string | ServiceConfigProps | undefined = dm.serviceName.find((sn: string | ServiceConfigProps) => {
            return typeof sn === 'string' ? url.path.includes(sn) : url.path.includes(sn.name)
          })
          // 存在服务则设置具体的域名和请求头
          if (service) {
            if (typeof service === 'string') { // 具体服务的
              header = dm.headers
              domain = dm.domainName
              timeout = dm.timeout
            } else {
              header = service.header || dm.headers
              domain = dm.domainName
              timeout = service.timeout || dm.timeout
            }
            break
          }
        }
      }
    }
    return {
      baseURL: domain || domainName,
      header: header || headers,
      timeout
    }
  }
  return {}
}

在request 中调用函数,同时对请求函数中的参数进行一个优先级的判断,函数优先级>配置
同时新增参数 mergeHeader 判断请求头是合并还是覆盖。因为可能存在不同的请求头的情况

/**
   * 发送请求
   * @param {URLInterface} url 接口
   * @param {*} params 参数
   * @param {RequestMethodProps} config 其余配置
   * @returns {Promise<any>}
   */
  request = (url: URLInterface, params: any = {}, config: RequestMethodProps = {}) => {
    // 接口的配置 默认get请求
    const { path, type = 'get' } = url
    // 将函数请求中的请求头和接触路径还有超时时间拿出
    const { headers = {}, baseURL: methodBaseURL, timeout: methodTimeout, mergeHeader = true, ...methodConfig } = config
    // 通过路径处理返回对应的域名和请求头、超时时间
    const { header = {}, baseURL, timeout }:HandleRequestResponse = handleRequest(url, this.requestConfig)
    // 整合请求头 基础域名 超时时间;优先级: 函数 > 配置
    // 请求头: 可能是覆盖或者合并的请求头,我们通过新增一个字段来控制是否合并或覆盖
    const baseConfig = {
      baseURL: methodBaseURL || baseURL,
      headers: mergeHeader ? { ...header,  ...headers } : (headers || header),
      timeout: methodTimeout || timeout
    }
    // 发送请求
    return this.instance.request({
      url: path,
      method: type,
      data: /(post|POST)/.test(type) ? params : undefined, // post请求方式
      params: /(get|GET)/.test(type) ? params : undefined, // get 请求方式
      paramsSerializer: (params) => qs.stringify(params, { arrayFormat: 'repeat' }),
      ...baseConfig,
      ...methodConfig
    })
  }

业务的扩展

目前的话,基本的使用没有问题了,然后就是考虑到可能有动态的请求头:如token等,或者需要对响应的请求体进行自定义的处理等。所以新增两个配置函数,用于处理自定义的响应处理和动态请求头新增。

首先修改AxiosProps 配置,新增函数

  • onDynamicHeader: 动态请求头
  • onHandleResponseData: 处理请求之后的数据: 业务数据处理

修改ts接口AxiosProps的定义

/** axios的参数配置 */
export interface AxiosProps {
  /** 请求的域名等 */
  requestConfig?: RequestConfigProps
  /** 超时的时间 */
  timeout?: number
  /** 关闭取消重复请求: 默认false 不关闭 */
  closeCancelRequest?: boolean
  /** 动态请求头 */
  onDynamicHeader?: () => any
  /**
   * 处理请求之后的数据: 业务数据处理
   * @param {AxiosResponse} response 响应的数据
   * @param {RequestMethodProps} config 函数请求的配置
   * @param {*} axios 当前请求的实体
   * @returns {Promise<any>}
   */
  onHandleResponseData?: (response: AxiosResponse, config?: RequestMethodProps, axios?: any) => Promise<any>
}

修改index.ts,修改constructor和request

/**
 * axios的封装
 */
import qs from 'qs'
import axios, { Axios, AxiosResponse } from 'axios'
import {
  URLInterface, RequestMethodProps, AxiosProps, RequestConfigProps,
  HandleRequestResponse, DomainAryProps, ServiceConfigProps
} from './index.inter'

//...其他代码

/** 初始化项目 */
export class BaseAxios {
  constructor (config?:AxiosProps) {
    this.closeCancelRequest = config?.closeCancelRequest // 默认是不关闭自动取消重复请求
    this.requestConfig = config?.requestConfig // 请求的配置
    this.timeout = config?.timeout // 请求超时时间
    this.config = config
    this.instance = axios.create()
    // 注册请求和响应的拦截器
    this.initInterceptors()
  }

  /** 请求的配置 */
  private config?: AxiosProps

  //...其他代码

  /**
   * 发送请求
   * @param {URLInterface} url 接口
   * @param {*} params 参数
   * @param {RequestMethodProps} config 其余配置
   * @returns {Promise<any>}
   */
  request = (url: URLInterface, params: any = {}, config: RequestMethodProps = {}) => {
    // 接口的配置 默认get请求
    const { path, type = 'get' } = url
    // 自定义函数的获取 
    const { onDynamicHeader, onHandleResponseData }:AxiosProps = this.config || {}
    // 将函数请求中的请求头和接触路径还有超时时间拿出
    const { headers = {}, baseURL: methodBaseURL, timeout: methodTimeout, mergeHeader = true, ...methodConfig } = config
    // 通过路径处理返回对应的域名和请求头、超时时间
    const { header = {}, baseURL, timeout }:HandleRequestResponse = handleRequest(url, this.requestConfig)
    // 整合请求头 基础域名 超时时间;优先级: 函数 > 配置
    // 请求头: 可能是覆盖或者合并的请求头,我们通过新增一个字段来控制是否合并或覆盖
    const dynamicHeader = onDynamicHeader ? onDynamicHeader() : {}
    const baseConfig = {
      baseURL: methodBaseURL || baseURL,
      headers: mergeHeader ? { ...dynamicHeader, ...header,  ...headers } : ({ ...dynamicHeader, ...(headers || header) }),
      timeout: methodTimeout || timeout || this.timeout
    }
    // 发送请求
    const instanceResponse = this.instance.request({
      url: path,
      method: type,
      data: /(post|POST)/.test(type) ? params : undefined, // post请求方式
      params: /(get|GET)/.test(type) ? params : undefined, // get 请求方式
      paramsSerializer: (params) => qs.stringify(params, { arrayFormat: 'repeat' }),
      ...baseConfig,
      ...methodConfig
    })
    if (onHandleResponseData) {
      return new Promise((resolve, reject) => {
        instanceResponse.then((response) => {
          onHandleResponseData(response, config, this)
          .then((res: any) => resolve(res))
          .catch((err) => reject(err))
        }).catch((err) => reject(err))
      })
    } else {
      return instanceResponse
    }
  }

  //...其他代码
}

结果测试

测试代码:Github 地址

// src/page/Service.tsx

import React from 'react'
import { Card, Space, Button } from 'antd'
import { BaseAxios } from '@components/axios'
import { allSettled } from '@util/index'
import type { AllSettledResponse } from '@util/index'
import { URLInterface, RequestConfigProps } from '@components/axios/index.inter'

const url1:URLInterface = {
  path: 'common1/weather/get15DaysWeatherByArea' // 请求路径不包含域名
}
const url2:URLInterface = {
  path: 'common2/weather/get15DaysWeatherByArea'
}
const url3:URLInterface = {
  path: 'common3/weather/get15DaysWeatherByArea'
}
const url4:URLInterface = {
  path: 'common4/weather/get15DaysWeatherByArea'
}

const config:RequestConfigProps = {
  domainName: 'https://api.apishop.net',
  headers: { Test: 'tes-a' },
  timeout: 45000,
  domainAry: [
    {
      domainName: 'https://api.apishop.net1',
      headers: { Test: 'tes-b' },
      timeout: 40000,
      serviceName: ['common1/', 'common2/'] // 不同的服务名
    },
    {
      domainName: 'https://api.apishop.net2',
      headers: { Test: 'tes-c' },
      serviceName: ['common3/', 'common4/']
    }
  ]
}

const axios = new BaseAxios({
  requestConfig: config,
  closeCancelRequest: true // 关闭重复的请求
})

export const ServiceItem = () => {
  const request = () => {
    allSettled([
      axios.request(url1), // 按照对应的域名来请求
      axios.request(url2), // 按照对应的域名来请求
      axios.request(url3), // 按照对应的域名来请求
      axios.request(url4), // 按照对应的域名来请求
      axios.request(url1, {}, { headers: { Test2: 'tes-d' } }), // 合并了请求头
      axios.request(url2, {}, { headers: { Test3: 'tes-d' }, mergeHeader: false }), // 覆盖请求头
    ]).then((responseAry:AllSettledResponse[]) => {
      responseAry.forEach((response:AllSettledResponse, index: number) => {
        if (response.status === 'success') {
          console.log('success:' + index, response.value)
        } else {
          console.log('failure:' + index, response.value)
        }
      })
    })
  }
  return <Card>
    <Space>
      <Button type='primary' onClick={request}>发送请求</Button>
    </Space>
  </Card>
}

完结

结束撒花🌹🌹

简单的将请求的进行了封装,使用的话,目前来说对于我来说够用了,当然还有一些针对于详细业务的处理比如说请求体呀,还有路径参数的处理,还有就是一个公共的参数的传递的,响应请求的统一的处理的,这些都需要自己根据自己实际的业务,和后端接口来进行设计编写。所以就考你们自己来啦。完结完结 🌹🌹🌹

项目源码:Github 地址