封装fetch和axios(超详细!!!)

345 阅读1分钟

前言

在前端开发中,AxiosFetch 是最常用的 HTTP 请求库。Axios 提供了更丰富的功能,而 Fetch 是原生的浏览器 API。为了更高的可维护性和复用性。本章实现AxiosFetch 的封装实现错误重试。

request.d.ts 文件

import { AxiosRequestConfig, AxiosResponse } from "axios";
import { FetcherFormProps } from "react-router-dom";
import { FetchModuleOptions } from "vite";
export interface RequestConfig { 
  show: boolean;  // 是否显示msg
  loading?: boolean; // 是否开启loading
  loadingText?: string; // loading开启文字
  message?: string; // 请求成功文案
}

export interface RequestOption extends RequestInit, AxiosRequestConfig { 
  requestType:"axios" | "fetch"
  withToken?: boolean;
  prefix?: string;
  retryCount?: number;
  retryTimeout?: number;
  requestHooks?: RequestHooks;
  tokenPrefix?: string;
  headTokenKey?: string;
  data?: any;
  url?: string;
  retry?: boolean;
  retryRequest?: (options: RequestOption, error: any) => any;
  file:FormData
}

export interface RequestHooks {
  afterRequest?: (response:any,config:RequestConfig) => any;
  beforeRequest?: (option:RequestOption,config:RequestConfig) => RequestOption;
}

handleMsg.ts (code码错误以及正常处理)

 


/**
 code码处理 
*/
import cache from "@/utils/cache"
import { RequestCodeEnum} from "@/enums/requestEnums"
import { RequestConfig } from "./request"
const handleMsg = (pageData: any, config: RequestConfig) => { 
  if (pageData.code === RequestCodeEnum.SUCCESS) {
    config?.show && window.$msg.success(config.message||pageData.msg || 'ok')
  } else if (RequestCodeEnum.TOKEN_INVALID.includes(pageData.code)) {
    window.$msg.error(pageData.msg || '请求错误')
    cache.remove('token')
    location.reload()
  } else if (pageData.code === RequestCodeEnum.ServerError) {
    window.$msg.error("请稍后重试....")
  } else { 
    window.$msg.error(pageData.msg || '请求错误')
  }
  
}
export default handleMsg

fetch 封装

import cache from '../cache' import handleMsg from './handleMsg' import { RequestConfig, RequestOption } from './request' import { RequestMethodsEnum } from '@/enums/requestEnums' import { TOKEN_KEY } from '@/enums/cacheEnums' const queryString = (params: Record<string, any>): string => {

return Object.keys(params).length > 0 ? '?' + Object.keys(params) .map((key) => ${key}=${encodeURIComponent(params[key])}) .join('&') : '' } const handleResponse = async (response: Response, config: RequestConfig): Promise => { const pageData = await response.json() if (response.ok) { handleMsg(pageData, config) return pageData } else { window.$msg.error(pageData.code + ':' + pageData.msg || pageData.code + ':' + '请求错误') throw new Error(pageData.code + ':' + (pageData.msg || '请求错误')) } } const fetchRequest = async (options: RequestOption, config: RequestConfig): Promise => { options.data = options.data || {} if (options.withToken) { options.headers[options.headTokenKey]=options.tokenPrefix+cache.getLocalStorage(TOKEN_KEY) } try { if (options.method === RequestMethodsEnum.GET) { options.baseURL = options.baseURL + queryString(options.data) } else { options.body = options.file? options.file: options.data&&JSON.stringify(options.data) } const controller = new AbortController() options.signal = controller.signal

const timer = setTimeout(() => {
  controller.abort()
}, options.timeout)
/*
可以做一些请求前的操作
*/

const response = await fetch(options.baseURL, options)
clearTimeout(timer)
return await handleResponse(response, config)

} catch (err: any) { /* 统一捕获一些错误的处理 */ window.$msg.error(err.message || '发送请求错误') if (options.retry && options.retryCount < 0 || !options.retry) { return new Error(err.message || '发送请求错误') } } if (options.retry && options.retryCount > 0) { await new Promise(resolve => setTimeout(resolve, options.retryTimeout)); options.retryCount-- return fetchRequest(options, config) } }

export default fetchRequest


# axios封装
```ts
import axios, { AxiosInstance } from 'axios'
import handleMsg from "./handleMsg"
import { RequestOption } from './request'
import cache from '../cache'
import { TOKEN_KEY } from '@/enums/cacheEnums'


const createAxiosRequest = (option:RequestOption):AxiosInstance => { 
  const service = axios.create(option)
  // ! 请求拦截器
  service.interceptors.request.use(
    config => {
      if (option.withToken) { 
        option.headers[option.headTokenKey]=option.tokenPrefix+cache.getLocalStorage(TOKEN_KEY)
      }
      config.headers = option.headers

      return config
    },
    error => {
      return Promise.reject(error)
    }
  )
  // ! 响应拦截器
  service.interceptors.response.use(
    response => {
      const config = response.config;
      const res = response.data
      handleMsg(res, config)
      return res
    },
    error => {
      window.$msg.error(error.message)
      error.config.retryCount--;
      if (error.config.retry && error.config.retryCount < 0 || !error.config.retry) { 
        return new Error(error) 
      }
      return  new Promise((resolve) => {
        setTimeout(() => {
          resolve(service(error.config));
        }, error.config.retryTimeout);
      })
    }
  )
  return service
}

export default createAxiosRequest

HttpRequest 类

import axios, { AxiosInstance } from "axios";
import { RequestConfig, RequestOption } from './request';
import createAxiosRequest from './axios';
import fetchRequest from "./fetch";
import { RequestMethodsEnum,ContentTypeEnum} from "@/enums/requestEnums"
let  axiosReuqest 
export default class HttpRequest { 
  private readonly option: RequestOption;
  constructor(option: RequestOption) {
    if (typeof option.requestHooks.beforeRequest=== 'function') {
      this.option =option.requestHooks.beforeRequest(option)
    } else { 
      throw new Error('requestHooks afterRequest is not a function')
    }
    if (this.option.requestType === 'axios') { 
      axiosReuqest = createAxiosRequest(this.option)
    }
  }
  get(option: RequestOption, config: RequestConfig):Promise<any>  { 
    return this.request({...option,method:RequestMethodsEnum.GET}, config)
  }
  post(option: RequestOption, config: RequestConfig): Promise<any> { 
    return this.request({...option,method:RequestMethodsEnum.POST}, config)
  }
  put(option: RequestOption, config: RequestConfig): Promise<any> {
    return this.request({...option,method:RequestMethodsEnum.PUT}, config)
  }
  delete(option: RequestOption, config: RequestConfig): Promise<any> {
    return this.request({...option,method:RequestMethodsEnum.DELETE}, config)
  }
  uploadFile(option: RequestOption, config: RequestConfig) { 
    option.headers['Content-Type'] = ContentTypeEnum.FORM_DATA
    const  method = RequestMethodsEnum.POST
    if (this.option.requestType === "axios") {
      option.data=option.file
      return axiosReuqest({
        ...option,
        ...config,
        method
      })
    } else { 
      option.baseURL=this.option.baseURL+option.url
     return fetchRequest({
        ...this.option,
        ...option,
        method
      }, config)
    }
  }

  private readonly request(option: RequestOption, config: RequestConfig): Promise<any> {
    
    if (this.option.requestType === 'axios') {
      return axiosReuqest({ ...option, ...config })
    } else { 
      option.baseURL = this.option.baseURL + option.url
      return fetchRequest({...this.option,...option}, config)
    }
  }
}

创建Http实例以及使用

import { RequestHooks, RequestOption } from "./request";
import cache from "@/utils/cache";
import { TOKEN_KEY } from "@/enums/cacheEnums"
import { ContentTypeEnum } from "@/enums/requestEnums"
import HttpRequest from "./http";
const requestHooks: RequestHooks = {
  beforeRequest: (option, config) => {
    let { withToken, headers, prefix, baseURL,tokenPrefix,headTokenKey,credentials,requestType,withCredentials } = option
    option.headers = headers ?? {}
    if (prefix) { 
      option.baseURL = baseURL + prefix
    }
    if (withCredentials && requestType === "fetch") { 
      option.credentials ="include"
    }
    if (withToken) {
      option.headers[headTokenKey] = tokenPrefix + cache.getCookie(TOKEN_KEY)
    }
    return option
  },
  
}
const defaultOptions:RequestOption = {
  baseURL:import.meta.env.VITE_APP_URL, // 服务器地址
  prefix: import.meta.env.VITE_APP_PREFIX, // /api
  headers: {
    'Content-Type':ContentTypeEnum.JSON,
  },
  withCredentials :true, // 是否开启cookie
  withToken: false, // 是否携带token(headders)
  requestHooks:requestHooks, // 请求hooks
  requestType: "axios", // 选择请求方式
  retryCount: 2, // 重试次数
  retryTimeout: 1000, // 重试请求时间
  tokenPrefix: "Bearer ", // token前缀
  headTokenKey: "Authorization", // token携带key
  timeout: 5000, // 请求超时时间
  retry: true, // 开启请求重试
};

export const createRequest = (options: RequestOption) => { 
  return new HttpRequest({
    ...defaultOptions,
    ...options
  })
}
const request = createRequest()

export default request

使用

import request from "@/utils/request"

//**
 get 请求
*/
// quert
cosnt aa =(data:any)=> request.get({url:"/xxxx",data})
// params
const aa = (id:number)=>request.get({url:`/xxxx/${id}`})
/**
post delete put 请求
*/
const aa = (data:any)=> request.post({url:"/xxxx",data},{show:true,message:"xxxx"})

/**
  上传图片
*/

const upload = (file:FormData)=>request.uploadFile({url:"/xxx",file},{show:true,message:"上传成功"})