使用TypeScript对axios请求进行二次封装

352 阅读5分钟

axios二次封装

1、安装依赖

npm i -S axios qs

2、创建axios实例

const baseURL = 'http://127.0.0.1:8080/api'
// 创建axiod实例
const service = axios.create({
  baseURL,
  timeout: 30000,
  withCredentials: false,
})

3、设置请求类型

// service.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'
service.defaults.headers.post['Content-Type'] = 'application/json;charset=utf-8'
service.defaults.headers.put['Content-Type'] = 'application/json;charset=utf-8'

4、配置多次请求取消方法

4.1、全局声明一个 Map 用于存储每个请求的标识 和 取消函数

const pending = new Map()

4.2、添加请求方法

/**
 * @description: 添加请求
 * @param {AxiosRequestConfig} config
 * @return {*}
 */
const addPending = (config: AxiosRequestConfig): void => {
  const url = [config.baseURL, config.method, config.url].join('')
  config.cancelToken = new axios.CancelToken(cancel => {
    if (!pending.has(url)) {
      // 如果 pending 中不存在当前请求,则添加进去
      pending.set(url, cancel)
    }
  })
}

4.3、移除请求方法

/**
 * @description: 移除请求
 *   移除未响应完的相同请求,避免重复请求
 * @param {AxiosRequestConfig} config
 * @return {*}
 */
const removePending = (config: AxiosRequestConfig): void => {
  const url = [config.baseURL, config.method, config.url].join('')
  if (pending.has(url)) {
    const cancel = pending.get(url)
    cancel(url)
    pending.delete(url)
  }
}

5、配置拦截器

5.1、请求拦截器

/**
 * 请求拦截器
 */
service.interceptors.request.use(
  (config: InternalAxiosRequestConfig) => {
    removePending(config) // 在请求开始前,移除未响应完的相同请求,避免重复请求
    addPending(config) // 将当前请求添加到 pending 中
    if (getToken()) {
      config.headers.Authorization = getToken()
    }
    return config
  },
  error => {
    console.log('请求异常', error)
    // 错误抛到业务代码
    error.data = {}
    error.data.code = -1
    error.data.message = '发送请求出现异常!'
    return Promise.reject(error)
  },
)

5.2、响应拦截器

/**
 * 响应拦截
 */
service.interceptors.response.use(
  (response: AxiosResponse) => {
    removePending(response) // 在请求结束后,移除本次请求
    if (response.status === 200) {
      // 请求结果正常
      const { code } = response.data
      if (code === 200) {
        // 请求成功
        return Promise.resolve(response.data)
      } else {
        // 处理系统自定义异常
        return Promise.reject(response.data)
      }
    } else {
      console.log('响应请求异常', response)
      return Promise.reject(response)
    }
  },
  error => {
    if (axios.isCancel(error)) {
      // 重复请求的错误
      // 中断promise
      return new Promise(() => {})
    }
    console.log('响应请求出现异常!', error)
    // 错误抛到业务代码
    error.data = {}
    error.data.code = -2
    error.data.message = '响应请求出现异常!'
    return Promise.reject(error.data)
  },
)

6、封装返回数据接口

interface HttpResponse<T> {
  code?: number
  data: T
  message?: string
}

7、完整代码

/*
 * @Author: PanZongHui
 * @Description: axios二次封装网络请求接口
 */
import { message } from 'antd'
import type { AxiosError, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
import axios from 'axios'
import Qs from 'qs'

// 导入redux中的store
import store from '@/store'

/**
 * 请求方法类型
 */
type Method = 'get' | 'post' | 'put' | 'delete'
/**
 * 响应错误类型
 */
type ResError = {
  code: number
  errMessage: string
}

/**
 * 响应结果数据接口 【根据后端返回格式定义】
 */
interface IResult<T> {
  code: number
  data: T
  message: string
}

// 设置后端请求地址
const baseURL = 'http://127.0.0.1:8080'

// 创建axios实例
const service = axios.create({
  baseURL,
  timeout: 30000,
  withCredentials: true,
})

const ContentType = {
  json: 'application/json;charset=utf-8', // json格式
  form: 'application/x-www-form-urlencoded;charset=UTF-8', // 表单
  multipart: 'multipart/form-data', // 文件上传
}

service.defaults.headers.post['Content-Type'] = ContentType.json
service.defaults.headers.put['Content-Type'] = ContentType.json

// 声明一个 Map 用于存储每个请求的标识 和 取消函数
const pending = new Map()
/**
 * @description: 添加请求
 * @param {AxiosRequestConfig} config
 */
const addPending = (config: AxiosRequestConfig) => {
  const url = [config.baseURL, config.method, config.url].join('')
  config.cancelToken = new axios.CancelToken(cancel => {
    if (!pending.has(url)) {
      pending.set(url, cancel) // 如果 pending 中不存在当前请求,则添加进去
    }
  })
}
/**
 * @description: 移除请求
 *   移除未响应完的相同请求,避免重复请求
 * @param {AxiosRequestConfig} config
 */
const removePending = (config: AxiosRequestConfig) => {
  const url = [config.baseURL, config.method, config.url].join('')
  if (pending.has(url)) {
    const cancel = pending.get(url)
    cancel(url)
    pending.delete(url)
  }
}

/**
 * @description: 获取token
 * @return {string|undefined}
 */
const getToken = (): string | undefined => store.getState().loginUser.token

/**
 * @description: 显示报错信息提示
 * @param {string} errorMessage
 */
const showErrorMessage = (errorMessage: string) => message.error(errorMessage)

/**
 * @description: 处理服务器响应异常
 * @param {AxiosError} error axios响应错误对象
 */
const handleResponseError = (error: AxiosError): ResError => {
  let errMessage = '服务器未知错误!'
  let code = -1
  // error.code="ERR_NETWORK"// 服务器已经停止
  // error.code="ERR_BAD_REQUEST" // 接口请求错误 404
  // error.code="ERR_BAD_RESPONSE" // 服务器响应异常 500
  if (error.code === 'ERR_NETWORK') {
    if (!window.navigator.onLine) {
      errMessage = '请检查你的网络是否正常!'
    } else {
      errMessage = '服务器运行异常,请联系管理人员!'
    }
  } else if (error.response) {
    // 处理服务器报错请求
    code = error.response.status
    switch (code) {
      case 400:
        errMessage = '错误的请求'
        break
      case 401:
        errMessage = '未授权,请重新登录'
        break
      case 403:
        errMessage = '拒绝访问'
        break
      case 404:
        errMessage = '请求错误,未找到该资源'
        break
      case 405:
        errMessage = '请求方法未允许'
        break
      case 408:
        errMessage = '请求超时'
        break
      case 500:
        errMessage = '服务器端出错'
        break
      case 501:
        errMessage = '网络未实现'
        break
      case 502:
        errMessage = '网络错误'
        break
      case 503:
        errMessage = '服务不可用'
        break
      case 504:
        errMessage = '网络超时'
        break
      case 505:
        errMessage = 'http版本不支持该请求'
        break
      default:
        errMessage = `其他连接错误 --${code}`
    }
  }
  return { code, errMessage }
}

/**
 * 请求拦截器
 */
service.interceptors.request.use((config: InternalAxiosRequestConfig) => {
  removePending(config) // 在请求开始前,移除未响应完的相同请求,避免重复请求
  addPending(config) // 将当前请求添加到 pending 中
  if (getToken()) {
    config.headers.Authorization = getToken()
  }
  return config
})

/**
 * 响应拦截器
 */
service.interceptors.response.use(
  (response: AxiosResponse) => {
    removePending(response) // 在请求结束后,移除本次请求
    if (response.status === 200) {
      // 请求结果正常
      const { code } = response.data
      if (code === 200) {
        // 请求成功
        return Promise.resolve(response.data)
      } else {
        const errMessage = response.data.message
        // 处理系统自定义异常
        console.log('这是系统自定义异常,异常信息为:' + errMessage)
        showErrorMessage(errMessage) // 系统后端自定义错误提示消息
        const resError: ResError = { code, errMessage }
        return Promise.reject(resError)
      }
    } else {
      const errMessage = '服务器响应异常!'
      console.log(errMessage, response)
      showErrorMessage(errMessage) // 错误提示
      const resError: ResError = { code: -2, errMessage }
      return Promise.reject(resError)
    }
  },
  error => {
    if (axios.isCancel(error)) {
      // 重复请求的错误 error:CanceledError
      // eslint-disable-next-line prettier/prettier
      return new Promise(() => { }) // 中断promise
    }
    // 服务器响应异常处理
    const resError = handleResponseError(error)
    console.log(resError, error)
    showErrorMessage(resError.errMessage) // 错误提示
    return Promise.reject(resError)
  },
)

/**
 * @description: axios二次封装 【需要try catch进行异常捕获】
 * @param {string} url 请求路径
 * @param {Method} method 请求方法
 * @param {unknown} data 传递数据
 * @param {AxiosRequestConfig} config 配置参数
 * @return {Promise<IResult<T>>}
 */
export const request = <T = any>(
  url: string,
  method: Method,
  data?: unknown,
  config?: AxiosRequestConfig,
): Promise<IResult<T>> => {
  let newConfig = { ...config }
  if (method === 'get' || method === 'delete') {
    newConfig = { ...config, paramsSerializer: data => Qs.stringify(data, { indices: false }) }
  }
  return service<T, IResult<T>>({
    url,
    method,
    // get,delete请求用params接收,其他请求用data
    [method.toLowerCase() === 'get' || method.toLowerCase() === 'delete' ? 'params' : 'data']: data,
    ...newConfig,
  })
}

/**
 * @description: axios二次封装升级版 【可以解决 try catch 过多问题,即:可以去除try catch】
 * @param {string} url 请求路径
 * @param {Method} method 请求方法
 * @param {unknown} data 传递数据
 * @param {AxiosRequestConfig} config 配置参数
 * @return {Promise<[U, undefined] | [null, IResult<T>]>} // 失败数据类型:[U, undefined] 成功数据类型:[null, IResult<T>]
 */
export const requestPro = <T = any, U = ResError>(
  url: string,
  method: Method,
  data?: unknown,
  config?: AxiosRequestConfig,
): Promise<[U, undefined] | [null, IResult<T>]> => {
  let newConfig = { ...config }
  if (method === 'get' || method === 'delete') {
    newConfig = { ...config, paramsSerializer: data => Qs.stringify(data, { indices: false }) }
  }

  return service<T, IResult<T>>({
    url,
    method,
    // get,delete请求用params接收,其他请求用data
    [method.toLowerCase() === 'get' || method.toLowerCase() === 'delete' ? 'params' : 'data']: data,
    ...newConfig,
  })
    .then<[null, IResult<T>]>(res => [null, res])
    .catch<[U, undefined]>(err => [err, undefined])
}