封装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:"上传成功"})
相关推荐
编程猪猪侠8 分钟前
Tailwind CSS 自定义工具类与主题配置指南
前端·css
qhd吴飞12 分钟前
mybatis 差异更新法
java·前端·mybatis
YGY Webgis糕手之路33 分钟前
OpenLayers 快速入门(九)Extent 介绍
前端·经验分享·笔记·vue·web
患得患失94936 分钟前
【前端】【vueDevTools】使用 vueDevTools 插件并修改默认打开编辑器
前端·编辑器
ReturnTrue86837 分钟前
Vue路由状态持久化方案,优雅实现记住表单历史搜索记录!
前端·vue.js
UncleKyrie42 分钟前
一个浏览器插件帮你查看Figma设计稿代码图片和转码
前端
遂心_44 分钟前
深入解析前后端分离中的 /api 设计:从路由到代理的完整指南
前端·javascript·api
你听得到111 小时前
Flutter - 手搓一个日历组件,集成单日选择、日期范围选择、国际化、农历和节气显示
前端·flutter·架构
风清云淡_A1 小时前
【REACT18.x】CRA+TS+ANTD5.X封装自定义的hooks复用业务功能
前端·react.js
@大迁世界1 小时前
第7章 React性能优化核心
前端·javascript·react.js·性能优化·前端框架