axios處理重復(fù)請(qǐng)求的方法小結(jié)
使用到的技術(shù)/庫(kù):axios
、TS
、發(fā)布訂閱者模式
。
本文將使用發(fā)布訂閱者
模式來(lái)處理重復(fù)的axios請(qǐng)求。將實(shí)現(xiàn)“一定時(shí)間內(nèi)發(fā)送多個(gè)相同的請(qǐng)求時(shí),只向服務(wù)器發(fā)出第一個(gè)請(qǐng)求,然后將第一個(gè)請(qǐng)求的響應(yīng)結(jié)果分發(fā)給各個(gè)請(qǐng)求方法”的效果。
前言
首先,我們要先定義何為重復(fù)的請(qǐng)求?
- 接口地址、類(lèi)型、參數(shù)相同,則視為相同的請(qǐng)求;
- 上一個(gè)請(qǐng)求還未返回響應(yīng)結(jié)果時(shí),又發(fā)送一個(gè)相同的請(qǐng)求,則視為重復(fù)請(qǐng)求。
其次,我們將每次請(qǐng)求生成一個(gè)唯一的key并緩存起來(lái),用作判斷是否已存在相同的請(qǐng)求。
最后,我們需要實(shí)現(xiàn)一個(gè)發(fā)布訂閱者模式
,在存在重復(fù)請(qǐng)求時(shí),中斷請(qǐng)求,并添加訂閱,當(dāng)之前的請(qǐng)求結(jié)果返回時(shí),發(fā)布給訂閱者。
還有一個(gè)問(wèn)題,如何中斷請(qǐng)求?只需在axios
的請(qǐng)求攔截器中返回一個(gè)Promise.reject()
即可,這樣將會(huì)直接執(zhí)行axios
的響應(yīng)攔截器中的錯(cuò)誤處理方法,而不會(huì)向服務(wù)器發(fā)送請(qǐng)求。
技術(shù)實(shí)現(xiàn)
1. 生成key
根據(jù)接口的地址、類(lèi)型、參數(shù),我們可以判斷是否為相同的請(qǐng)求,那我們可以在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ù)類(lèi)型是為了處理FormData
、二進(jìn)制等格式情況。
2. 緩存請(qǐng)求
我們可以創(chuàng)建一個(gè)全局變量來(lái)保存請(qǐng)求信息,在請(qǐng)求攔截器(發(fā)送請(qǐng)求之前)中將請(qǐng)求信息添加至全局變量,然后在響應(yīng)攔截器(請(qǐ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; // 緩存請(qǐng)求信息 historyRequests.set(key, 1); return config; }, (error) => { return Promise.reject(error); } ); instance.interceptors.response.use( (res) => { // 請(qǐng)求完成,刪除緩存信息 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)
訂閱者通過(guò)注冊(cè)事件到調(diào)度中心,當(dāng)發(fā)布者觸發(fā)事件時(shí),由調(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; // 判斷是否存在相同請(qǐng)求,存在則中斷請(qǐng)求 if (historyRequests.has(key)) { // 為了后續(xù)方便處理中斷請(qǐng)求超時(shí) config.headers.requestTime = Date.now(); // 拋出錯(cuò)誤并傳遞相應(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都為錯(cuò)誤 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ù)請(qǐng)求,則發(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ù)請(qǐng)求 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); // 處理超時(shí) 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)代碼,例如:你需要對(duì)某些請(qǐng)求放行,則可以通過(guò)在headers
中添加一個(gè)屬性來(lái)控制是否允許重復(fù)請(qǐng)求。
以上就是axios處理重復(fù)請(qǐng)求的方法小結(jié)的詳細(xì)內(nèi)容,更多關(guān)于axios處理重復(fù)請(qǐng)求的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaScript實(shí)現(xiàn)判斷圖片是否加載完成的3種方法整理
這篇文章主要介紹了JavaScript實(shí)現(xiàn)判斷圖片是否加載完成的3種方法整理,本文講解了onload方法、javascipt原生方法、jquery方法三種方法,需要的朋友可以參考下2015-03-03微信小程序?qū)崿F(xiàn)的貪吃蛇游戲【附源碼下載】
這篇文章主要介紹了微信小程序?qū)崿F(xiàn)的貪吃蛇游戲,結(jié)合實(shí)例形式分析了微信小程序?qū)崿F(xiàn)貪吃蛇游戲功能的相關(guān)界面布局與代碼邏輯操作技巧,并附帶源碼供讀者下載參考,需要的朋友可以參考下2018-01-01JavaScript腳本性能優(yōu)化注意事項(xiàng)
本文總結(jié)了我在JavaScript編程中所找到的提高JavaScript運(yùn)行性能的一些方法,其實(shí)這些經(jīng)驗(yàn)都基于幾條原則2008-11-11JavaScript實(shí)現(xiàn)99乘法表及隔行變色實(shí)例代碼
最近做了個(gè)項(xiàng)目是要求實(shí)現(xiàn)99乘法表隔行變色,本文給大家分享通過(guò)多種方式實(shí)現(xiàn)js 99 乘法表,感興趣的朋友一起看看吧2016-02-02clipboard.js在移動(dòng)端復(fù)制失敗的解決方法
最近在使用clipboard.js碰到的一個(gè)小問(wèn)題,通過(guò)查找相關(guān)資料解決了,所以下面這篇文章主要給大家介紹了關(guān)于clipboard.js在移動(dòng)端復(fù)制失敗的解決方法,需要的朋友可以參考借鑒,下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-06-06解決JS使用fill()進(jìn)行數(shù)組填充遇到的問(wèn)題
最近在做算法題時(shí),遇到需要?jiǎng)?chuàng)建二維數(shù)組并進(jìn)行初始化的情況,剛開(kāi)始我使用的是 new Array(n).fill(new Array(n).fill('.')) 進(jìn)行二維數(shù)組的初始化,但無(wú)論怎樣我都通不過(guò)測(cè)試用例,所以本文就給大家詳細(xì)的介紹了如何解決這類(lèi)問(wèn)題以及將js中的fill(方法重學(xué)一下2023-09-09