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

前言

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

request.d.ts 文件

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码错误以及正常处理)

ts 复制代码
 


/**
 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 封装

ts 复制代码
import handleMsg from './handleMsg'
import { RequestConfig, RequestOption } from './request'
import { RequestMethodsEnum } from '@/enums/requestEnums'
/**
 处理拼接地址query传参
*/
const queryString = (params: Record<string, any>): string => {
  return Object.keys(params).length > 0
    ? '?' +
        Object.keys(params)
          .map((key) => `${key}=${encodeURIComponent(params[key])}`)
          .join('&')
    : ''
}
/**
  处理请求返回内容以及显示提示
  response.ok 为true判断是否给提示 并且返回后端传回的内容
  response.ok 如果为false的话状态码一般都是错误的直接抛出错误
  
*/
const handleResponse = async (response: Response, config: RequestConfig): Promise<any> => {
  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<any> => {
  /**
   判断是否是get请求在拼接get请求的数据
  */
  try {
    if (options.method === RequestMethodsEnum.GET) {
      options.baseURL = options.baseURL + queryString(options.data)
    } else {
      options.body = options.file? options.file: 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) {
    /*
    统一捕获一些错误的处理
    */
    /**
     错误重试
     判断是否开启重试 并且重试次数是否小于0 或者 错误重试没开启 直接放回错误信息
    */
    window.$msg.error(err.message || '发送请求错误')
    if (options.retry && options.retryCount < 0 || !options.retry) {
      return new Error(err.message || '发送请求错误')
     }
  }
  /**
      如果重试开启并且重试次数大于0开始发请求
  */
  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'


const createAxiosRequest = (option:RequestOption):AxiosInstance => { 
  const service = axios.create(option)
  // ! 请求拦截器
  service.interceptors.request.use(
    config => {
      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 类

ts 复制代码
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实例以及使用

ts 复制代码
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

使用

ts 复制代码
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:"上传成功"})
相关推荐
hackeroink1 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者3 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-3 小时前
验证码机制
前端·后端
燃先生._.4 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖5 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235245 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240256 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar6 小时前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人7 小时前
前端知识补充—CSS
前端·css