最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

前端JavaScript徹底解決重復(fù)請求問題的五種方案

 更新時間:2025年11月24日 09:19:49   作者:南城FE  
在前端開發(fā)中,重復(fù)請求是一個常見且棘手的問題,比如用戶快速點擊保存按鈕導(dǎo)致生成多條重復(fù)單據(jù),或者列表頁頻繁刷新造成服務(wù)器壓力飆升,本文將系統(tǒng)梳理重復(fù)請求的解決方案,從基礎(chǔ)到進階進行對比分析,并結(jié)合實際代碼案例解決這一痛點,需要的朋友可以參考下

引言

在前端開發(fā)中,重復(fù)請求是一個常見且棘手的問題。比如用戶快速點擊"保存"按鈕導(dǎo)致生成多條重復(fù)單據(jù),或者列表頁頻繁刷新造成服務(wù)器壓力飆升,這些場景不僅影響用戶體驗,還可能引發(fā)數(shù)據(jù)一致性問題。本文將系統(tǒng)梳理重復(fù)請求的解決方案,從基礎(chǔ)到進階進行對比分析,并結(jié)合實際代碼案例解決這一痛點。

一、重復(fù)請求不止是"多花錢"

在討論解決方案前,我們先明確重復(fù)請求的具體影響,避免因"覺得問題不大"而忽視它:

  • 數(shù)據(jù)一致性風(fēng)險:如表單重復(fù)提交導(dǎo)致生成多個相同訂單、重復(fù)創(chuàng)建用戶,后續(xù)需要額外成本修復(fù)數(shù)據(jù)
  • 服務(wù)器資源浪費:相同請求反復(fù)發(fā)送,占用帶寬和服務(wù)器算力,極端情況下可能引發(fā)服務(wù)過載
  • 前端體驗降級:重復(fù)請求可能導(dǎo)致頁面多次渲染閃爍,或觸發(fā)多次錯誤提示
  • 網(wǎng)絡(luò)資源消耗:尤其在移動端,重復(fù)請求會浪費用戶流量,增加加載時間

了解危害后,我們來看當(dāng)前主流的解決方案,及其適用場景和優(yōu)缺點。

二、5種重復(fù)請求解決方案對比

方案1:UI層面控制(最簡單但不徹底)

這是最基礎(chǔ)的解決方案,通過控制UI交互阻止重復(fù)觸發(fā)請求,核心思路是"讓用戶無法重復(fù)點擊"。

實現(xiàn)方式

  • 按鈕點擊后立即禁用,直到請求完成(成功/失敗)后重新啟用
  • 列表刷新時顯示加載狀態(tài),禁止再次觸發(fā)刷新操作
  • 路由切換時取消當(dāng)前頁面未完成的請求

代碼示例(React)

const SaveButton = () => {  
 const [loading, setLoading] = useState(false);  
  
 const handleSave = async () => {  
 if (loading) return; // 防止重復(fù)觸發(fā)  
 setLoading(true);  
 try {  
 await api.submitForm(data);  
 message.success("保存成功");  
 } catch (error) {  
 message.error("保存失敗");  
 } finally {  
 setLoading(false); // 請求完成后恢復(fù)按鈕狀態(tài)  
 }  
 };  
  
 return <Button loading={loading} onClick={handleSave}>保存</Button>;  
};  

優(yōu)缺點分析

實現(xiàn)簡單,無額外依賴
對現(xiàn)有代碼侵入性低
即時反饋,提升用戶體驗

無法覆蓋所有場景(如代碼層面直接調(diào)用接口)
多個組件調(diào)用同一接口時,無法共享狀態(tài)
無法處理網(wǎng)絡(luò)延遲導(dǎo)致的"隱性重復(fù)請求"

適用場景

  • 簡單表單提交、單按鈕交互場景
  • 快速迭代的小型項目,無復(fù)雜接口調(diào)用邏輯

方案2:請求攔截器+緩存(適合讀操作)

對于查詢類接口(如列表查詢、詳情獲?。?,可通過"請求攔截器+緩存"實現(xiàn)重復(fù)請求攔截,核心思路是"相同請求只發(fā)一次,結(jié)果緩存復(fù)用"。

實現(xiàn)原理

  • 定義緩存容器(如Map),存儲已發(fā)送但未完成的請求Promise
  • 發(fā)起請求前,生成請求唯一標(biāo)識(如URL+參數(shù)+方法的哈希值)
  • 若緩存中存在該請求的Promise,直接返回緩存的Promise;若不存在,發(fā)送請求并將Promise存入緩存
  • 請求完成(成功/失?。┖?,清除緩存,確保下次請求可正常發(fā)起

代碼示例(Axios攔截器)

import axios from 'axios';  
import { sha256 } from 'js-sha256';  
  
// 緩存容器:key=請求唯一標(biāo)識,value=請求Promise  
const requestCache = new Map();  
  
// 創(chuàng)建Axios實例  
const service = axios.create({  
 baseURL: import.meta.env.VITE_API_BASE_URL,  
 timeout: 5000  
});  
  
// 請求攔截器  
service.interceptors.request.use(  
 (config) => {  
 // 1. 生成請求唯一標(biāo)識(URL+方法+參數(shù))  
 const requestKey = generateRequestKey(config);  
   
 // 2. 檢查緩存:若存在未完成的請求,直接返回緩存的Promise  
 if (requestCache.has(requestKey)) {  
 return requestCache.get(requestKey);  
 }  
   
 // 3. 若不存在緩存,發(fā)送請求并緩存Promise  
 const requestPromise = Promise.resolve(config);  
 requestCache.set(requestKey, requestPromise);  
 return requestPromise;  
 },  
 (error) => Promise.reject(error)  
);  
  
// 響應(yīng)攔截器  
service.interceptors.response.use(  
 (response) => {  
 // 請求完成,清除緩存  
 const requestKey = generateRequestKey(response.config);  
 requestCache.delete(requestKey);  
 return response.data;  
 },  
 (error) => {  
 // 請求失敗,同樣清除緩存(避免緩存失敗狀態(tài))  
 if (error.config) {  
 const requestKey = generateRequestKey(error.config);  
 requestCache.delete(requestKey);  
 }  
 return Promise.reject(error);  
 }  
);  
  
// 生成請求唯一標(biāo)識:基于URL、方法、params、data的哈希值  
function generateRequestKey(config) {  
 const { url, method, params, data } = config;  
 const requestStr = JSON.stringify({ url, method, params, data });  
 // 使用sha256生成哈希值,確保唯一性  
 return sha256(requestStr);  
}  
  
export default service;  

優(yōu)缺點分析

優(yōu)點:
對業(yè)務(wù)代碼無侵入,全局生效
減少重復(fù)請求,減輕服務(wù)器壓力
支持多組件共享請求結(jié)果

缺點:
不適合寫操作(如新增/修改/刪除),可能導(dǎo)致數(shù)據(jù)更新不及時
緩存有效期難控制,需手動處理過期邏輯
無法處理請求取消場景

適用場景

  • 讀操作接口(如列表查詢、詳情獲取、下拉選單數(shù)據(jù)加載)
  • 無實時數(shù)據(jù)要求的場景,允許短期緩存

方案3:請求取消+狀態(tài)管理(適合寫操作)

對于寫操作接口(如新增、修改、刪除),不能使用緩存(需確保每次請求都能觸達服務(wù)器),此時需通過"請求取消+狀態(tài)管理"實現(xiàn)重復(fù)攔截,核心思路是"相同寫請求同時只能存在一個,重復(fù)請求直接取消"。

實現(xiàn)原理

  • 維護一個請求狀態(tài)容器,存儲當(dāng)前未完成的寫請求標(biāo)識及對應(yīng)的取消函數(shù)
  • 發(fā)起寫請求前,生成請求唯一標(biāo)識,檢查容器:若存在相同請求,調(diào)用取消函數(shù)取消新請求
  • 若不存在相同請求,創(chuàng)建AbortController(或CancelToken),將取消函數(shù)和請求標(biāo)識存入容器
  • 請求完成(成功/失?。┗蛉∠螅瑥娜萜髦幸瞥撜埱髽?biāo)識

代碼示例(結(jié)合AbortController)

import axios from 'axios';  
import { sha256 } from 'js-sha256';  
  
// 管理未完成的寫請求:key=請求唯一標(biāo)識,value=AbortController  
const pendingWriteRequests = new Map();  
  
// 寫請求專用Axios實例  
const writeService = axios.create({  
 baseURL: import.meta.env.VITE_API_BASE_URL,  
 timeout: 5000  
});  
  
// 發(fā)起寫請求(如POST/PUT/DELETE)  
export function sendWriteRequest(config) {  
 // 1. 生成請求唯一標(biāo)識  
 const requestKey = generateRequestKey(config);  
   
 // 2. 檢查是否存在未完成的相同請求:若有,取消新請求  
 if (pendingWriteRequests.has(requestKey)) {  
 const newController = new AbortController();  
 // 取消新請求  
 newController.abort('重復(fù)請求已取消');  
 return Promise.reject(new Error('重復(fù)請求已取消'));  
 }  
   
 // 3. 創(chuàng)建AbortController,用于取消請求  
 const controller = new AbortController();  
 const newConfig = {  
 ...config,  
 signal: controller.signal // 綁定取消信號  
 };  
   
 // 4. 將請求標(biāo)識和取消控制器存入容器  
 pendingWriteRequests.set(requestKey, controller);  
   
 // 5. 發(fā)送請求,完成后清除容器  
 return writeService(newConfig)  
 .then((response) => {  
 pendingWriteRequests.delete(requestKey);  
 return response.data;  
 })  
 .catch((error) => {  
 pendingWriteRequests.delete(requestKey);  
 // 過濾"主動取消"的錯誤,避免業(yè)務(wù)層處理  
 if (error.name === 'AbortError') {  
 console.log('請求已取消:', requestKey);  
 return Promise.reject(new Error('請求已取消'));  
 }  
 return Promise.reject(error);  
 });  
}  
  
// 生成請求唯一標(biāo)識(同方案2)  
function generateRequestKey(config) {  
 const { url, method, params, data } = config;  
 const requestStr = JSON.stringify({ url, method, params, data });  
 return sha256(requestStr);  
}  
  
// 手動取消指定請求(如頁面卸載時)  
export function cancelWriteRequest(config) {  
 const requestKey = generateRequestKey(config);  
 if (pendingWriteRequests.has(requestKey)) {  
 const controller = pendingWriteRequests.get(requestKey);  
 controller.abort('手動取消請求');  
 pendingWriteRequests.delete(requestKey);  
 }  
}  
  
export default writeService;  

優(yōu)缺點分析

優(yōu)點:
適合寫操作,確保數(shù)據(jù)一致性
支持手動取消(如頁面卸載)
避免重復(fù)寫請求導(dǎo)致的數(shù)據(jù)問題

缺點:
實現(xiàn)較復(fù)雜,需手動管理取消邏輯
對業(yè)務(wù)代碼有一定侵入性(需使用專用請求函數(shù))
無法復(fù)用請求結(jié)果,每次請求都需觸達服務(wù)器

適用場景

  • 寫操作接口(如表單提交、數(shù)據(jù)修改、刪除操作)
  • 對數(shù)據(jù)一致性要求高的場景(如訂單創(chuàng)建、支付請求)

方案4:訂閱-發(fā)布模式(多訂閱者共享請求結(jié)果)

當(dāng)多個組件同時調(diào)用同一接口時,可通過"訂閱-發(fā)布模式"實現(xiàn)"一次請求,多端復(fù)用",核心思路是"相同請求只發(fā)送一次,結(jié)果分發(fā)給所有訂閱者",這也是參考范文中采用的核心方案。

實現(xiàn)原理

  • 維護一個請求狀態(tài)容器:key=請求唯一標(biāo)識,value=訂閱者列表+請求Promise
  • 組件發(fā)起請求時,生成請求唯一標(biāo)識,檢查容器:
  • 若請求已存在(未完成):將當(dāng)前組件的回調(diào)函數(shù)加入訂閱者列表
  • 若請求不存在:發(fā)送請求,將Promise存入容器,并添加當(dāng)前組件的訂閱者
  • 請求完成后,遍歷訂閱者列表,將結(jié)果分發(fā)給所有訂閱者
  • 訂閱者取消訂閱(如組件卸載)時,從訂閱者列表中移除自身

代碼示例(基于參考范文封裝)

import axios from 'axios';  
import { sha256 } from 'js-sha256';  
  
class RequestSubscriber {  
 // 容器:key=請求唯一標(biāo)識,value={ promise: 請求Promise, subscribers: 訂閱者列表 }  
 constructor() {  
 this.requestStore = new Map();  
 this.instance = axios.create({  
 baseURL: import.meta.env.VITE_API_BASE_URL,  
 timeout: 5000  
 });  
 }  
  
 // 發(fā)起請求(訂閱)  
 request(config) {  
 const requestKey = this.generateRequestKey(config);  
 const storeItem = this.requestStore.get(requestKey);  
  
 // 1. 若請求已存在,添加訂閱者  
 if (storeItem) {  
 return new Promise((resolve, reject) => {  
 storeItem.subscribers.push({ resolve, reject });  
 });  
 }  
  
 // 2. 若請求不存在,創(chuàng)建請求并訂閱  
 const subscribers = [];  
 const controller = new AbortController();  
 const newConfig = { ...config, signal: controller.signal };  
  
 // 創(chuàng)建請求Promise  
 const requestPromise = this.instance(newConfig)  
 .then((response) => {  
 // 請求成功,通知所有訂閱者  
 this.notifySubscribers(requestKey, 'resolve', response.data);  
 return response.data;  
 })  
 .catch((error) => {  
 // 請求失敗,通知所有訂閱者  
 this.notifySubscribers(requestKey, 'reject', error);  
 return Promise.reject(error);  
 })  
 .finally(() => {  
 // 請求完成,清除容器  
 this.requestStore.delete(requestKey);  
 });  
  
 // 存入容器  
 this.requestStore.set(requestKey, {  
 promise: requestPromise,  
 subscribers,  
 controller  
 });  
  
 // 返回當(dāng)前訂閱的Promise  
 return new Promise((resolve, reject) => {  
 subscribers.push({ resolve, reject });  
 });  
 }  
  
 // 通知所有訂閱者  
 notifySubscribers(requestKey, type, data) {  
 const storeItem = this.requestStore.get(requestKey);  
 if (!storeItem) return;  
  
 storeItem.subscribers.forEach((subscriber) => {  
 subscriber[type](data);  
 });  
 }  
  
 // 取消請求(如組件卸載)  
 cancelRequest(config) {  
 const requestKey = this.generateRequestKey(config);  
 const storeItem = this.requestStore.get(requestKey);  
 if (storeItem) {  
 // 取消請求  
 storeItem.controller.abort('請求已取消');  
 // 清除容器  
 this.requestStore.delete(requestKey);  
 }  
 }  
  
 // 生成請求唯一標(biāo)識  
 generateRequestKey(config) {  
 const { url, method, params, data } = config;  
 const requestStr = JSON.stringify({ url, method, params, data });  
 return sha256(requestStr).slice(0, 40); // 截取前40位,平衡唯一性和長度  
 }  
}  
  
// 單例模式:確保全局只有一個實例  
export const requestSubscriber = new RequestSubscriber();  

優(yōu)缺點分析

優(yōu)點:
多組件共享請求結(jié)果,減少請求次數(shù)
支持請求取消,避免內(nèi)存泄漏
兼顧讀操作和寫操作(寫操作可關(guān)閉共享)

缺點:
實現(xiàn)復(fù)雜,需維護訂閱者列表和請求狀態(tài)
調(diào)試難度高,需跟蹤訂閱者和請求狀態(tài)
對新手不友好,需理解訂閱-發(fā)布模式

適用場景

  • 多組件同時調(diào)用同一接口的場景(如多個組件需要同一批下拉選單數(shù)據(jù))
  • 大型項目,需統(tǒng)一管理請求狀態(tài)和訂閱關(guān)系

方案5:后端配合攔截(最徹底的方案)

前端方案雖能解決大部分場景,但仍存在"極端情況漏洞"(如網(wǎng)絡(luò)延遲導(dǎo)致的請求繞過前端攔截),此時需后端配合,從源頭攔截重復(fù)請求,核心思路是"后端基于唯一標(biāo)識判斷是否為重復(fù)請求"。

實現(xiàn)原理

  • 前端發(fā)起請求時,生成一個唯一標(biāo)識(如UUID),存入請求頭(如X-Request-ID
  • 后端接收到請求后,檢查X-Request-ID
  • 若Redis中不存在該ID:處理請求,并將ID存入Redis(設(shè)置過期時間,如5秒)
  • 若Redis中已存在該ID:判定為重復(fù)請求,直接返回"重復(fù)請求"錯誤
  • 前端接收到"重復(fù)請求"錯誤后,提示用戶或忽略該響應(yīng)

代碼示例(前后端配合)

前端部分

import axios from 'axios';  
import { v4 as uuidv4 } from 'uuid';  
  
const service = axios.create({  
 baseURL: import.meta.env.VITE_API_BASE_URL,  
 timeout: 5000  
});  
  
// 請求攔截器:添加唯一請求ID  
service.interceptors.request.use(  
 (config) => {  
 // 生成唯一請求ID(UUID)  
 const requestId = uuidv4();  
 // 存入請求頭  
 config.headers['X-Request-ID'] = requestId;  
 // 存入localStorage,用于后續(xù)重復(fù)請求判斷(可選)  
 localStorage.setItem(`request_${requestId}`, 'pending');  
 return config;  
 },  
 (error) => Promise.reject(error)  
);  
  
// 響應(yīng)攔截器:處理重復(fù)請求錯誤  
service.interceptors.response.use(  
 (response) => {  
 const requestId = response.config.headers['X-Request-ID'];  
 // 請求完成,刪除localStorage中的標(biāo)識  
 localStorage.removeItem(`request_${requestId}`);  
 return response.data;  
 },  
 (error) => {  
 if (error.response?.data?.code === 'DUPLICATE_REQUEST') {  
 // 后端返回重復(fù)請求錯誤,提示用戶  
 message.warning('請勿重復(fù)操作');  
 const requestId = error.config.headers['X-Request-ID'];  
 localStorage.removeItem(`request_${requestId}`);  
 return Promise.reject(new Error('重復(fù)請求已攔截'));  
 }  
 return Promise.reject(error);  
 }  
);  
  
export default service;  
``

**后端部分(Node.js + Redis)**:

``const express = require('express');  
const redis = require('redis');  
const { v4: uuidv4 } = require('uuid');  
  
const app = express();  
const redisClient = redis.createClient({  
 url: process.env.REDIS_URL  
});  
redisClient.connect();  
  
// 重復(fù)請求攔截中間件  
app.use(async (req, res, next) => {  
 const requestId = req.headers['x-request-id'];  
 if (!requestId) {  
 return res.status(400).json({ code: 'INVALID_REQUEST', message: '缺少請求ID' });  
 }  
  
 // 檢查Redis中是否存在該請求ID  
 const exists = await redisClient.exists(`request:${requestId}`);  
 if (exists) {  
 // 已存在,判定為重復(fù)請求  
 return res.status(400).json({ code: 'DUPLICATE_REQUEST', message: '重復(fù)請求已攔截' });  
 }  
  
 // 不存在,存入Redis(設(shè)置5秒過期,避免內(nèi)存泄漏)  
 await redisClient.setEx(`request:${requestId}`, 5, 'pending');  
 next();  
});  
  
// 業(yè)務(wù)接口  
app.post('/api/submit-form', (req, res) => {  
 // 處理表單提交邏輯  
 res.json({ code: 'SUCCESS', message: '提交成功' });  
});  
  
app.listen(3000, () => {  
 console.log('Server running on port 3000');  
});  

優(yōu)缺點分析

優(yōu)點:
從源頭攔截重復(fù)請求,最徹底
不受前端環(huán)境影響(如多標(biāo)簽頁、多設(shè)備)
支持分布式系統(tǒng),可跨服務(wù)判斷重復(fù)請求

缺點:
需后端配合,增加后端開發(fā)成本
依賴Redis等存儲服務(wù),增加部署復(fù)雜度
需處理請求ID的過期邏輯,避免存儲膨脹

適用場景

  • 對數(shù)據(jù)一致性要求極高的場景(如支付、訂單創(chuàng)建)
  • 大型分布式系統(tǒng),前端攔截?zé)o法覆蓋所有場景

三、推薦組合方案實現(xiàn)"徹底解決"

單一方案無法覆蓋所有場景,實際項目中建議采用"組合方案",兼顧性能、體驗和數(shù)據(jù)一致性:

  • 基礎(chǔ)層:方案1(UI控制)+ 方案2(緩存)
  • 所有按鈕點擊添加loading狀態(tài),防止重復(fù)觸發(fā)
  • 所有讀操作接口添加緩存,減少服務(wù)器壓力
  • 核心層:方案3(請求取消)+ 方案4(訂閱-發(fā)布)
  • 所有寫操作接口添加請求取消邏輯,避免重復(fù)提交
  • 多組件共享的接口使用訂閱-發(fā)布模式,提升性能
  • 保障層:方案5(后端配合)
  • 核心業(yè)務(wù)接口(如支付、訂單)添加后端重復(fù)攔截
  • 前端傳遞唯一請求ID,后端基于Redis判斷重復(fù)

通過這種"三層防護",可徹底解決前端重復(fù)請求問題,同時兼顧開發(fā)效率和系統(tǒng)穩(wěn)定性。

以上就是前端JavaScript徹底解決重復(fù)請求問題的五種方案的詳細內(nèi)容,更多關(guān)于JavaScript解決重復(fù)請求的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論

永昌县| 团风县| 九台市| 武陟县| 青田县| 开化县| 双城市| 成安县| 满洲里市| 北票市| 曲麻莱县| 海淀区| 武定县| 博兴县| 连云港市| 巴彦淖尔市| 大足县| 梁山县| 黑山县| 城固县| 宁化县| 青神县| 兰溪市| 壤塘县| 右玉县| 黄梅县| 陇川县| 海兴县| 宜阳县| 阿拉善右旗| 中卫市| 前郭尔| 肃宁县| 辉南县| 浦城县| 定南县| 炎陵县| 道孚县| 雅江县| 普定县| 桂东县|