准备工作和内容大概
- 安装axios和axios-retry
- 封装成一个通用类Vaxios
- 基于插件化的思想,扩展axios功能
- 导出特殊用法的request、http、axios方法
安装依赖
pnpm add axios axios-retry
yarn add axios axios-retry
npm i axios axios-retry
目录结构

Vaxios基于axios封装的类
cancel实现取消重复请求
jsonp实现jsonp请求
plugins基于扩展内容封装成一个个插件
types定义的一些常用类型
index主导出入口
扩展AxiosRequestConfig配置项
declare module 'axios' {
interface AxiosRequestConfig {
'axios-retry'?: IAxiosRetryConfigExtended;
}
}
declare module 'axios' {
interface AxiosRequestConfig {
jsonp?: JsonpConfig
requestKey?: number | string | symbol
ignoreCancelToken?: boolean
}
}
一些类型定义
import { Vaxios } from './Vaxios'
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
import type { JsonpConfig } from './jsonp'
export type RequestInterceptor = {
onFulfilled: Parameters<UseRequestInterceptors>['0']
onRejected: Parameters<UseRequestInterceptors>['1']
}
export type ResponseInterceptor = {
onFulfilled: Parameters<UseResponseInterceptors>['0']
onRejected: Parameters<UseResponseInterceptors>['1']
}
export type UseRequestInterceptors<V = AxiosRequestConfig> = (
onFulfilled?: ((value: V) => V | void) | null,
onRejected?: ((error: any) => void) | null,
) => RequestInterceptor
export type UseResponseInterceptors<V = AxiosResponse> = (
onFulfilled?: ((value: V) => Partial<V> | void) | null,
onRejected?: ((error: any) => void) | null,
) => ResponseInterceptor
export type Plugin = (
vAxios: Vaxios,
axios: AxiosInstance,
config: AxiosRequestConfig,
) => void
export { Vaxios }
declare module 'axios' {
interface AxiosRequestConfig {
jsonp?: JsonpConfig
requestKey?: number | string | symbol
ignoreCancelToken?: boolean
}
}
基于axios封装Vaxios类
import axios from 'axios'
import { merge } from 'lodash-unified'
import type { AxiosInstance, AxiosRequestConfig } from 'axios'
import type {
Plugin,
RequestInterceptor,
ResponseInterceptor,
UseRequestInterceptors,
UseResponseInterceptors,
} from './types'
export class Vaxios {
private axios: AxiosInstance
private config: AxiosRequestConfig
private requestInterceptors = new Array<RequestInterceptor>()
private responseInterceptors = new Array<ResponseInterceptor>()
private pluginArr = new Array<Plugin>()
constructor(config: AxiosRequestConfig) {
this.axios = axios.create(config)
this.config = config
this.setupInterceptors()
}
public use(plugin: Plugin) {
plugin(this, this.axios, this.config)
this.pluginArr.push(plugin)
return this
}
public plugins(pluginArr: Array<Plugin>) {
pluginArr.forEach((plugin) => this.use(plugin))
}
public getAxios() {
return this.axios
}
private setupInterceptors() {
this.axios.interceptors.request.use(
(config) => {
const configArr = this.requestInterceptors
.map(({ onFulfilled }) => onFulfilled?.(config))
.filter((item) => !!item && typeof item === 'object')
return merge(config, ...configArr)
},
(error) => {
this.requestInterceptors.forEach(({ onRejected }) =>
onRejected?.(error),
)
return Promise.reject(error)
},
)
this.axios.interceptors.response.use(
(response) => {
const responseArr = this.responseInterceptors
.map(({ onFulfilled }) => onFulfilled?.(response))
.filter((item) => !!item && typeof item === 'object')
return merge(response, ...responseArr)
},
(error) => {
this.responseInterceptors.forEach(({ onRejected }) =>
onRejected?.(error),
)
return Promise.reject(error)
},
)
}
useRequestInterceptors: UseRequestInterceptors = (
onFulfilled,
onRejected,
) => {
const interceptors = { onFulfilled, onRejected }
this.requestInterceptors.push(interceptors)
return interceptors
}
useRequestCatchInterceptors(onRejected: RequestInterceptor['onRejected']) {
return this.useRequestInterceptors(null, onRejected)
}
useResponseInterceptors: UseResponseInterceptors = (
onFulfilled,
onRejected,
) => {
const interceptors = { onFulfilled, onRejected }
this.responseInterceptors.push(interceptors)
return interceptors
}
useResponseCatchInterceptors(onRejected: ResponseInterceptor['onRejected']) {
return this.useResponseInterceptors(null, onRejected)
}
offRequestInterceptor(interceptor: RequestInterceptor) {
const idx = this.requestInterceptors.indexOf(interceptor)
if (idx >= 0) {
this.requestInterceptors.splice(idx, 1)
return true
}
return false
}
offResponseInterceptor(interceptor: ResponseInterceptor) {
const idx = this.responseInterceptors.indexOf(interceptor)
if (idx >= 0) {
this.responseInterceptors.splice(idx, 1)
return true
}
return false
}
}
export default Vaxios
实现取消重复请求功能
封装AxiosCanceler类
import type { AxiosRequestConfig } from 'axios'
const pendingMap = new Set<string>()
const getPendingUrl = (config: AxiosRequestConfig): string => {
return [config.method, config.url].join('&')
}
export class AxiosCanceler {
public addPending(config: AxiosRequestConfig): void {
const url = getPendingUrl(config)
const controller = new AbortController()
if (pendingMap.has(url)) {
config.signal = controller.signal
controller.abort(url)
} else {
pendingMap.add(url)
}
}
public removeAllPending(): void {
this.reset()
}
public removePending(config: AxiosRequestConfig): void {
const url = getPendingUrl(config)
if (pendingMap.has(url)) {
requestAnimationFrame(() => {
pendingMap.delete(url)
})
}
}
public reset(): void {
pendingMap.clear()
}
}
实现cancelPlugin 插件
import axios from 'axios'
import { AxiosCanceler } from './cancel'
import type {
AxiosError,
AxiosRequestConfig,
} from 'axios'
import type { Vaxios } from './types'
export const cancelPlugin = (vAxios: Vaxios) => {
const axiosCanceler = new AxiosCanceler()
vAxios.useRequestInterceptors((config) => {
const ignoreCancelToken = config?.ignoreCancelToken
if (!ignoreCancelToken) {
axiosCanceler.addPending(config)
}
})
vAxios.useResponseInterceptors(
(response) => {
const config = response.config
const ignoreCancelToken = config?.ignoreCancelToken
if (response && !ignoreCancelToken) {
axiosCanceler.removePending(config)
}
},
(error) => {
if (axios.isCancel(error)) {
return
}
const config = (error as AxiosError)?.config
if (config && !config.ignoreCancelToken) {
axiosCanceler.removePending(config as AxiosRequestConfig)
}
},
)
}
实现jsonp功能
- jsonp的实现是参考axios-jsonp这个包
- jsonp主要是用axios的adapter去实现的
实现jsonpAdapter
import axios from 'axios'
import type {
AxiosPromise,
AxiosRequestConfig,
AxiosResponse,
InternalAxiosRequestConfig,
} from 'axios'
let cid = 1
function buildParams(params: Record<string, any>) {
const result: Array<string> = []
for (const i in params) {
result.push(
`${window.encodeURIComponent(i)}=${window.encodeURIComponent(params[i])}`,
)
}
return result.join('&')
}
export type JsonpConfig = {
callbackName?: string
}
export default function jsonpAdapter(
config: AxiosRequestConfig,
jsonpConfig: JsonpConfig,
) {
return new Promise((resolve, reject) => {
let script: HTMLScriptElement | null = document.createElement('script')
let src = config.url ?? ''
const isAbort = !!config.signal?.aborted
const onError = () => {
remove()
reject(
new axios.AxiosError(
'Jsonp Error',
'JSONP_ERROR',
config as InternalAxiosRequestConfig,
),
)
}
if (isAbort) {
onError()
return
}
if (config.params) {
const params = buildParams(config.params)
if (params) {
src += (src.includes('?') ? '&' : '?') + params
}
}
script.async = true
function remove() {
if (script) {
script.onload = script.onreadystatechange = script.onerror = null
if (script.parentNode) {
script.parentNode.removeChild(script)
}
script = null
}
}
const jsonp = `axiosJsonpCallback${cid++}`
const old = window[jsonp]
window[jsonp] = function (responseData) {
window[jsonp] = old
if (isAbort) {
return
}
const response: AxiosResponse = {
data: responseData,
headers: {},
statusText: 'ok',
config: config as InternalAxiosRequestConfig,
status: 200,
}
resolve(response)
}
const additionalParams = {
_: Date.now(),
}
additionalParams[jsonpConfig.callbackName || 'callback'] = jsonp
src += (src.includes('?') ? '&' : '?') + buildParams(additionalParams)
script.onload = script.onreadystatechange = function () {
if (!script.readyState || /loaded|complete/.test(script.readyState)) {
remove()
}
}
script.onerror = function () {
onError()
}
script.src = src
document.head.appendChild(script)
}) as AxiosPromise
}
实现jsonpPlugin
import axiosJsonpAdapter from './jsonp'
import axios from 'axios'
import type {
AxiosInstance,
AxiosPromise,
AxiosRequestConfig,
} from 'axios'
export const jsonpPlugin = (_: Vaxios, axiosInstance: AxiosInstance) => {
const adapter = axiosInstance.defaults.adapter
const jsonpAdapter = (config: AxiosRequestConfig): AxiosPromise => {
if (config.jsonp) {
return axiosJsonpAdapter(config, config.jsonp)
}
config.adapter = adapter
return axios(config)
}
axiosInstance.defaults.adapter = jsonpAdapter
}
实现出现错误重新请求功能
- 基于axios-retry这个包的功能
- 基于vaxios的高扩展性实现retryPlugin插件
import axiosRetry from 'axios-retry'
import type {
AxiosInstance,
AxiosRequestConfig,
} from 'axios'
import type { Vaxios } from './types'
export const retryPlugin = (
_: Vaxios,
axiosInstance: AxiosInstance,
config: AxiosRequestConfig,
) => {
const retry = config?.['axios-retry']
axiosRetry(axiosInstance, {
...(retry || {}),
retries: retry?.retries || 3,
retryDelay: retry?.retryDelay || axiosRetry.exponentialDelay,
retryCondition(error) {
if (axios.isCancel(error)) {
return false
}
return retry?.retryCondition?.(error) ?? true
},
})
}
组合我们的插件并导出
import axiosRetry from 'axios-retry'
import axios from 'axios'
import axiosJsonpAdapter from './jsonp'
import { AxiosCanceler } from './cancel'
import type {
AxiosError,
AxiosInstance,
AxiosPromise,
AxiosRequestConfig,
} from 'axios'
import type { Vaxios } from './types'
export const retryPlugin = (
_: Vaxios,
axiosInstance: AxiosInstance,
config: AxiosRequestConfig,
) => {
const retry = config?.['axios-retry']
axiosRetry(axiosInstance, {
...(retry || {}),
retries: retry?.retries || 3,
retryDelay: retry?.retryDelay || axiosRetry.exponentialDelay,
retryCondition(error) {
if (axios.isCancel(error)) {
return false
}
return retry?.retryCondition?.(error) ?? true
},
})
}
export const jsonpPlugin = (_: Vaxios, axiosInstance: AxiosInstance) => {
const adapter = axiosInstance.defaults.adapter
const jsonpAdapter = (config: AxiosRequestConfig): AxiosPromise => {
if (config.jsonp) {
return axiosJsonpAdapter(config, config.jsonp)
}
config.adapter = adapter
return axios(config)
}
axiosInstance.defaults.adapter = jsonpAdapter
}
export const cancelPlugin = (vAxios: Vaxios) => {
const axiosCanceler = new AxiosCanceler()
vAxios.useRequestInterceptors((config) => {
const ignoreCancelToken = config?.ignoreCancelToken
if (!ignoreCancelToken) {
axiosCanceler.addPending(config)
}
})
vAxios.useResponseInterceptors(
(response) => {
const config = response.config
const ignoreCancelToken = config?.ignoreCancelToken
if (response && !ignoreCancelToken) {
axiosCanceler.removePending(config)
}
},
(error) => {
if (axios.isCancel(error)) {
return
}
const config = (error as AxiosError)?.config
if (config && !config.ignoreCancelToken) {
axiosCanceler.removePending(config as AxiosRequestConfig)
}
},
)
}
export default [retryPlugin, jsonpPlugin, cancelPlugin]
index.ts入口文件导出
import axios from 'axios'
import Vaxios from './Vaxios'
import plugins from './plugins'
import type { AxiosError, AxiosRequestConfig } from 'axios'
export * from './types'
export * from './Vaxios'
export * from './plugins'
const request = async <D = any, E = AxiosError>(
conf: AxiosRequestConfig,
vaxios: Vaxios,
): Promise<[E, null] | [null, D]> => {
let response = null
let error = null
const requestKey = Symbol('requestKey')
const interceptor = vaxios.useResponseCatchInterceptors(
(axiosError: AxiosError) => {
const { config } = axiosError ?? {}
if (config?.requestKey === requestKey) {
error = axiosError
}
},
)
try {
response = await vaxios.getAxios().request({ ...conf, requestKey })
} catch (err) {
error = err
}
vaxios.offResponseInterceptor(interceptor)
return [error as E, response?.data]
}
export const createRequest = (config: AxiosRequestConfig) => {
const vaxios = new Vaxios(config)
vaxios.plugins(plugins)
const currentRequest = <D = any, E = AxiosError>(
conf: AxiosRequestConfig,
) => {
return request<D, E>(conf, vaxios)
}
currentRequest['vaxios'] = vaxios
currentRequest['axios'] = vaxios.getAxios()
return currentRequest
}
createRequest['axios'] = axios
export default createRequest
使用方式
项目中创建axios.ts文件
import { createRequest as _ } from '@xxxx/axios'
import type { Plugin } from '@xxxx/axios'
_.axios.defaults.timeout = 6000
const $ = _({
headers: { 'Content-Type': 'application/json;charset=UTF-8' },
})
$.vaxios.useRequestInterceptors(
(conf) => {
console.log(conf, 'conf')
return {
headers: {
},
}
},
(err) => {
console.log(err, '请求错误')
},
)
$.vaxios.useResponseInterceptors(
(response) => {
console.log(response, 'response')
return {
data: {
},
}
},
(err) => {
console.log(err, '响应错误')
},
)
const checkErrorStatusPlugin: Plugin = (vaxios) => {
vaxios.useResponseCatchInterceptors((error) => {
let errMessage = ''
const status = error?.response?.status
const msg: string = error?.response?.data?.error?.message ?? ''
switch (status) {
case 400:
errMessage = `${msg}`
break
case 401:
errMessage = msg || '用户没有权限(令牌、用户名、密码错误)!'
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:
}
if (errMessage) {
window.confirm(errMessage)
}
})
}
$.vaxios.use(checkErrorStatusPlugin)
$.axios.interceptors.request.use(
(config) => {
return config
},
(error) => {
console.log(error, '请求错误拦截')
},
)
$.axios.interceptors.response.use(
(response) => {
return response
},
(error) => {
console.log(error, '响应错误拦截')
},
)
export default $
export const request = $.axios.request
export const http = $.axios.post
项目中使用
import request from "./axios"
type Data={
name:string,
password:string
}
const [error, data] = await request<Data>({
url: 'https://example/xxx',
method:'GET',
"axios-retry":{
retries:3
},
ignoreCancelToken:false,
})