axios處理重復(fù)請求的方法小結(jié)
使用到的技術(shù)/庫:axios、TS、發(fā)布訂閱者模式。
本文將使用發(fā)布訂閱者模式來處理重復(fù)的axios請求。將實(shí)現(xiàn)“一定時間內(nèi)發(fā)送多個相同的請求時,只向服務(wù)器發(fā)出第一個請求,然后將第一個請求的響應(yīng)結(jié)果分發(fā)給各個請求方法”的效果。
前言
首先,我們要先定義何為重復(fù)的請求?
- 接口地址、類型、參數(shù)相同,則視為相同的請求;
- 上一個請求還未返回響應(yīng)結(jié)果時,又發(fā)送一個相同的請求,則視為重復(fù)請求。
其次,我們將每次請求生成一個唯一的key并緩存起來,用作判斷是否已存在相同的請求。
最后,我們需要實(shí)現(xiàn)一個發(fā)布訂閱者模式,在存在重復(fù)請求時,中斷請求,并添加訂閱,當(dāng)之前的請求結(jié)果返回時,發(fā)布給訂閱者。
還有一個問題,如何中斷請求?只需在axios的請求攔截器中返回一個Promise.reject()即可,這樣將會直接執(zhí)行axios的響應(yīng)攔截器中的錯誤處理方法,而不會向服務(wù)器發(fā)送請求。
技術(shù)實(shí)現(xiàn)
1. 生成key
根據(jù)接口的地址、類型、參數(shù),我們可以判斷是否為相同的請求,那我們可以在key中包含這些關(guān)鍵信息。
import { type AxiosRequestConfig } from 'axios'
const getDataType = (obj: unknown) => {
let res = Object.prototype.toString.call(obj).split(' ')[1]
res = res.substring(0, res.length - 1).toLowerCase()
return res
}
const getKey = (config: AxiosRequestConfig) => {
const { method, url, data, params } = config;
let key = `${method}-${url}`;
try {
if (data && getDataType(data) === 'object') {
key += `-${JSON.stringify(data)}`;
} else if (getDataType(data) === 'formdata') {
for (const [k, v] of data.entries()) {
if (v instanceof Blob) {
continue;
}
key += `-${k}-${v}`;
}
}
if (params && getDataType(params) === 'object') {
key += `-${JSON.stringify(params)}`;
}
} catch (e) {console.error(e);}
return key;
};
判斷參數(shù)類型是為了處理FormData、二進(jìn)制等格式情況。
2. 緩存請求
我們可以創(chuàng)建一個全局變量來保存請求信息,在請求攔截器(發(fā)送請求之前)中將請求信息添加至全局變量,然后在響應(yīng)攔截器(請求完成之后)中移除相關(guān)信息。
import axios from 'axios';
const historyRequests = new Map<string, number>()
axios.interceptors.request.use(
(config) => {
// 生成key
const key = createKey(config);
// 響應(yīng)攔截器中需要用到
config.headers.key = key;
// 緩存請求信息
historyRequests.set(key, 1);
return config;
},
(error) => {
return Promise.reject(error);
}
);
instance.interceptors.response.use(
(res) => {
// 請求完成,刪除緩存信息
const key = res.config.headers.key as string;
if (historyRequests.has(key)) {
historyRequests.delete(key);
}
return res;
},
(error) => {
return Promise.reject(error);
}
);
3. 發(fā)布訂閱者模式實(shí)現(xiàn)
訂閱者通過注冊事件到調(diào)度中心,當(dāng)發(fā)布者觸發(fā)事件時,由調(diào)度中心執(zhí)行相應(yīng)的訂閱事件。
export default class EventBus<T extends Record<string | symbol, any>> {
private listeners: Record<keyof T, ((...args: any[]) => void)[]> = {} as any
$on<K extends keyof T>(event: K, callback: T[K]) {
if (!this.listeners[event]) {
this.listeners[event] = []
}
this.listeners[event].push(callback)
}
$emit<K extends keyof T>(event: K, ...args: Parameters<T[K]>) {
const callbacks = this.listeners[event]
if (!callbacks || callbacks?.length === 0) {
return
}
callbacks.forEach((callback) => {
callback(...args)
})
}
$off<K extends keyof T>(event: K, listener?: T[K]) {
if (!listener) {
delete this.listeners[event]
return
}
const fns = this.listeners[event]
if (!fns || !fns.length) {
return
}
const idx = fns.indexOf(listener)
if (idx !== -1) {
fns.splice(idx, 1)
}
}
clear() {
this.listeners = {} as any
}
}
4. 完整代碼實(shí)現(xiàn)
import axios, {
type AxiosRequestConfig,
AxiosError,
type AxiosResponse
} from 'axios';
import { getDataType } from './utils';
import { EventBus } from './event/eventBus';
interface IBaseResponse {
code: number;
msg: string;
data?: unknown;
}
const createKey = (config: AxiosRequestConfig) => {
const { method, url, data, params } = config;
let key = `${method}-${url}`;
try {
if (data && getDataType(data) === 'object') {
key += `-${JSON.stringify(data)}`;
} else if (getDataType(data) === 'formdata') {
for (const [k, v] of data.entries()) {
if (v instanceof Blob) {
continue
}
key += `-${k}-${v}`;
}
}
if (params && getDataType(params) === 'object') {
key += `-${JSON.stringify(params)}`;
}
} catch (e) {console.error(e);}
return key;
};
const instance = axios.create({
baseURL: '',
timeout: 5000,
withCredentials: true,
headers: {
'Content-Type': 'application/json;'
}
});
const historyRequests = new Map<string, number>();
instance.interceptors.request.use(
(config) => {
const key = createKey(config);
// 在響應(yīng)攔截器中需要用到該key發(fā)布/訂閱事件
config.headers.key = key;
// 判斷是否存在相同請求,存在則中斷請求
if (historyRequests.has(key)) {
// 為了后續(xù)方便處理中斷請求超時
config.headers.requestTime = Date.now();
// 拋出錯誤并傳遞相應(yīng)參數(shù)
return Promise.reject(
new AxiosError('Redundant request', 'ERR_REPEATED', config)
);
}
historyRequests.set(key, 1);
return config;
},
(error: AxiosError) => {
return Promise.reject(error);
}
);
const responseInterceptor = (res: AxiosResponse<IBaseResponse | Blob>) => {
const result: [
AxiosResponse<IBaseResponse | Blob> | undefined,
AxiosError | undefined
] = [undefined, undefined];
const data = res.data;
// 可根據(jù)你的接口響應(yīng)數(shù)據(jù)作處理,這里假設(shè)code不為200都為錯誤
if (data instanceof Blob || data.code === 200) {
result[0] = res;
} else {
result[1] = new AxiosError(data.msg);
}
return result;
};
const eventBus = new EventBus<{
[key: string]: (
data?: AxiosResponse<IBaseResponse | Blob>,
error?: AxiosError
) => void;
}>();
instance.interceptors.response.use(
(res) => {
const [data, error] = responseInterceptor(res);
// 如果存在重復(fù)請求,則發(fā)布結(jié)果,執(zhí)行訂閱事件
const key = res.config.headers.key as string;
if (historyRequests.has(key)) {
historyRequests.delete(key);
eventBus.$emit(key, data, error);
}
return data !== undefined ? data : Promise.reject(error);
},
(error: AxiosError) => {
// 處理中斷的重復(fù)請求
if (error.code === 'ERR_REPEATED') {
return new Promise((resolve, reject) => {
const config = error.config!;
const key = config.headers.key as string;
const callback = (
res?: AxiosResponse<IBaseResponse | Blob>,
err?: AxiosError
) => {
res ? resolve(res) : reject(err);
eventBus.$off(key, callback);
};
// 訂閱事件
eventBus.$on(key, callback);
// 處理超時
const timeout = config.timeout || 5000;
const requestTime = config.headers.requestTime as number;
const now = Date.now();
if (now - requestTime > timeout) {
historyRequests.delete(key);
const error = new AxiosError(
`timeout of ${timeout}ms exceeded`,
'ECONNABORTED',
config
);
error.name = 'AxiosError';
eventBus.$emit(key, undefined, error);
}
});
}
return Promise.reject(error);
}
);
可以根據(jù)你的實(shí)際需求調(diào)整相關(guān)代碼,例如:你需要對某些請求放行,則可以通過在headers中添加一個屬性來控制是否允許重復(fù)請求。
以上就是axios處理重復(fù)請求的方法小結(jié)的詳細(xì)內(nèi)容,更多關(guān)于axios處理重復(fù)請求的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
javascript返回頂部的按鈕實(shí)現(xiàn)方法
這篇文章主要介紹了javascript返回頂部的按鈕實(shí)現(xiàn)方法,需要的朋友可以參考下2016-01-01
JavaScript在form表單中使用button按鈕實(shí)現(xiàn)submit提交方法
在form表單提交中,使用button來間接完成submit的提交更為靈活。接下來通過本文給大家介紹JavaScript在form表單中使用button按鈕實(shí)現(xiàn)submit提交方法,需要的朋友參考下吧2017-01-01
微信小程序?qū)崿F(xiàn)自動播放視頻模仿gif動圖效果實(shí)例
這篇文章主要給大家介紹了關(guān)于微信小程序?qū)崿F(xiàn)自動播放視頻模仿gif動圖效果的相關(guān)資料,通過本文介紹的方法可以實(shí)現(xiàn)自動播放視頻,視頻無控制條無聲音且自動循環(huán)播放,需要的朋友可以參考下2021-07-07
JS動態(tài)添加Table的TR,TD實(shí)現(xiàn)方法
這篇文章主要介紹了JS動態(tài)添加Table的TR,TD實(shí)現(xiàn)方法,涉及js針對Table中TR、TD節(jié)點(diǎn)的操作技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-01-01
微信小程序之自定義組件的實(shí)現(xiàn)代碼(附源碼)
最近在項(xiàng)目開發(fā)中,遇到好多雷同的頁面樣式,就想著可以將常用的功能模塊封裝成組件,方便在項(xiàng)目中使用和修改。這篇文章主要介紹了微信小程序之自定義組件的實(shí)現(xiàn)代碼(附源碼),需要的朋友可以參考下2018-08-08
JavaScript使用Range調(diào)色及透明度實(shí)例
本文給大家分享的是使用range做的一個簡單的手動調(diào)色并可以得到RGB值的小工具,非常的實(shí)用,有需要的小伙伴可以參考下2016-09-09

