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

前端防止接口重復請求的四種方案

 更新時間:2026年02月11日 09:32:17   作者:DEMO派  
文章介紹了幾種常見的防止接口重復請求的方案,并推薦了分層防重復策略,建議根據(jù)業(yè)務場景選擇合適的方案組合,以確保用戶體驗和系統(tǒng)穩(wěn)定性,需要的朋友可以參考下

一、常見方案分析

1.1 Loading狀態(tài)方案

// 方案1: 按鈕Loading狀態(tài)
class RequestController {
  constructor() {
    this.loading = false;
  }

  async fetchData(params) {
    if (this.loading) {
      console.log('請求正在進行中,請稍后...');
      return;
    }
    
    try {
      this.loading = true;
      // 顯示loading UI
      this.showLoading();
      
      const response = await axios.get('/api/data', { params });
      return response.data;
    } catch (error) {
      console.error('請求失敗:', error);
      throw error;
    } finally {
      this.loading = false;
      // 隱藏loading UI
      this.hideLoading();
    }
  }

  showLoading() {
    // 顯示全局loading或按鈕loading
  }

  hideLoading() {
    // 隱藏loading
  }
}

優(yōu)點

  • 實現(xiàn)簡單,用戶體驗直觀
  • 能有效防止重復點擊
  • 適用于按鈕點擊場景

缺點

  • 無法防止并行多個不同請求
  • 需要手動管理loading狀態(tài)
  • 頁面多個按鈕需要分別處理

1.2 請求標記方案

// 方案2: 請求唯一標識
class RequestMarker {
  constructor() {
    this.pendingRequests = new Map();
  }

  async request(url, config = {}) {
    const requestKey = this.generateKey(url, config.params || config.data);
    
    // 檢查是否已有相同請求
    if (this.pendingRequests.has(requestKey)) {
      // 可選:返回已有請求的Promise
      return this.pendingRequests.get(requestKey);
    }

    // 創(chuàng)建新的請求
    const requestPromise = axios({
      url,
      ...config,
    }).finally(() => {
      // 請求完成后移除標記
      this.pendingRequests.delete(requestKey);
    });

    // 存儲請求
    this.pendingRequests.set(requestKey, requestPromise);
    
    return requestPromise;
  }

  generateKey(url, params) {
    const sortedParams = params ? 
      Object.keys(params).sort().map(key => `${key}=${params[key]}`).join('&') : '';
    return `${url}?${sortedParams}`;
  }

  // 手動取消特定請求
  cancelRequest(url, params) {
    const key = this.generateKey(url, params);
    if (this.pendingRequests.has(key)) {
      // 實際項目中可能需要使用axios的cancel token
      this.pendingRequests.delete(key);
    }
  }
}

優(yōu)點

  • 精確控制重復請求
  • 可復用已有請求Promise
  • 支持請求取消

缺點

  • 實現(xiàn)相對復雜
  • 需要維護請求映射表
  • 內(nèi)存占用需要考慮

1.3 防抖/節(jié)流方案

// 方案3: 防抖請求
class DebounceRequest {
  constructor(wait = 500) {
    this.timer = null;
    this.lastRequestTime = 0;
  }

  // 防抖版本
  debounceRequest(url, params, wait = 500) {
    return new Promise((resolve, reject) => {
      if (this.timer) {
        clearTimeout(this.timer);
      }

      this.timer = setTimeout(async () => {
        try {
          const response = await axios.get(url, { params });
          resolve(response.data);
        } catch (error) {
          reject(error);
        }
      }, wait);
    });
  }

  // 節(jié)流版本
  throttleRequest(url, params, interval = 1000) {
    return new Promise((resolve, reject) => {
      const now = Date.now();
      
      if (now - this.lastRequestTime < interval) {
        console.log('請求過于頻繁');
        reject(new Error('請求過于頻繁'));
        return;
      }

      this.lastRequestTime = now;
      
      axios.get(url, { params })
        .then(response => resolve(response.data))
        .catch(error => reject(error));
    });
  }
}

優(yōu)點

  • 有效控制請求頻率
  • 減少服務器壓力
  • 適用于搜索框等高頻場景

缺點

  • 可能延遲必要請求
  • 不適合所有場景
  • 需要合理設置時間間隔

1.4 請求攔截器方案

// 方案4: 攔截器全局控制
class AxiosInterceptor {
  constructor() {
    this.pendingRequests = new Map();
    this.setupInterceptors();
  }

  setupInterceptors() {
    // 請求攔截器
    axios.interceptors.request.use(config => {
      // 生成請求唯一標識
      const requestKey = this.generateRequestKey(config);
      
      // 取消重復請求
      if (this.pendingRequests.has(requestKey)) {
        config.cancelToken = new axios.CancelToken(cancel => {
          cancel('重復請求,已取消');
        });
      } else {
        // 存儲cancel函數(shù)
        config.cancelToken = config.cancelToken || new axios.CancelToken(cancel => {
          this.pendingRequests.set(requestKey, cancel);
        });
      }
      
      return config;
    });

    // 響應攔截器
    axios.interceptors.response.use(
      response => {
        // 請求完成后移除
        const requestKey = this.generateRequestKey(response.config);
        this.pendingRequests.delete(requestKey);
        return response;
      },
      error => {
        if (axios.isCancel(error)) {
          console.log('請求被取消:', error.message);
          return Promise.reject(error);
        }
        
        // 錯誤時也移除請求
        if (error.config) {
          const requestKey = this.generateRequestKey(error.config);
          this.pendingRequests.delete(requestKey);
        }
        
        return Promise.reject(error);
      }
    );
  }

  generateRequestKey(config) {
    const { method, url, params, data } = config;
    let key = `${method}-${url}`;
    
    if (params) {
      key += `-${JSON.stringify(params)}`;
    }
    
    if (data) {
      key += `-${JSON.stringify(data)}`;
    }
    
    return key;
  }

  // 取消所有pending請求
  cancelAllRequests() {
    this.pendingRequests.forEach(cancel => {
      cancel();
    });
    this.pendingRequests.clear();
  }
}

優(yōu)點

  • 全局統(tǒng)一處理
  • 與業(yè)務代碼解耦
  • 支持精細化控制

缺點

  • 配置復雜
  • 可能影響正常請求
  • 需要處理邊界情況

1.5 React Hooks方案

// 方案5: React自定義Hook
import { useRef, useState, useCallback } from 'react';

function usePreventDuplicateRequest() {
  const [loading, setLoading] = useState(false);
  const requestRef = useRef(null);
  const pendingRequests = useRef(new Map());

  // 帶loading的請求
  const requestWithLoading = useCallback(async (requestFn, ...args) => {
    if (loading) {
      console.log('已有請求在進行中');
      return;
    }

    setLoading(true);
    try {
      const result = await requestFn(...args);
      return result;
    } finally {
      setLoading(false);
    }
  }, [loading]);

  // 防重復請求
  const requestWithPrevention = useCallback(async (key, requestFn, ...args) => {
    // 檢查是否有相同請求
    if (pendingRequests.current.has(key)) {
      console.log('重復請求,使用緩存');
      return pendingRequests.current.get(key);
    }

    try {
      const requestPromise = requestFn(...args);
      pendingRequests.current.set(key, requestPromise);
      
      const result = await requestPromise;
      return result;
    } finally {
      pendingRequests.current.delete(key);
    }
  }, []);

  // 防抖請求
  const debounceRequest = useCallback((requestFn, delay = 500) => {
    return debounce(requestFn, delay);
  }, []);

  return {
    loading,
    requestWithLoading,
    requestWithPrevention,
    debounceRequest,
  };
}

// 使用示例
function SearchComponent() {
  const { loading, requestWithLoading } = usePreventDuplicateRequest();
  
  const handleSearch = async (keyword) => {
    return requestWithLoading(
      () => axios.get('/api/search', { params: { q: keyword } })
    );
  };

  return (
    <div>
      <button 
        onClick={handleSearch} 
        disabled={loading}
      >
        {loading ? '搜索中...' : '搜索'}
      </button>
    </div>
  );
}

二、綜合方案建議

推薦實現(xiàn):分層防重復策略

// 綜合方案:分層防重復
class ComprehensiveRequestManager {
  constructor(options = {}) {
    this.options = {
      enableLoading: true,
      enableDebounce: true,
      enableRequestDedupe: true,
      debounceTime: 300,
      ...options
    };
    
    this.loadingStates = new Map();
    this.pendingRequests = new Map();
    this.debounceTimers = new Map();
  }

  // 核心請求方法
  async request(config) {
    const {
      url,
      method = 'GET',
      params,
      data,
      requestId,
      showLoading = true,
      useDebounce = false,
    } = config;

    // 1. 生成請求唯一標識
    const requestKey = requestId || this.generateRequestKey({ url, method, params, data });

    // 2. 防抖處理
    if (useDebounce && this.options.enableDebounce) {
      return this.debounceRequest(requestKey, () => 
        this.executeRequest(requestKey, config, showLoading)
      );
    }

    // 3. 直接執(zhí)行請求
    return this.executeRequest(requestKey, config, showLoading);
  }

  // 執(zhí)行請求
  async executeRequest(requestKey, config, showLoading) {
    // 檢查重復請求
    if (this.options.enableRequestDedupe && this.pendingRequests.has(requestKey)) {
      console.log(`請求 ${requestKey} 已存在,返回已有Promise`);
      return this.pendingRequests.get(requestKey);
    }

    // 顯示loading
    if (showLoading && this.options.enableLoading) {
      this.setLoading(requestKey, true);
    }

    try {
      // 創(chuàng)建請求Promise
      const requestPromise = axios({
        ...config,
        cancelToken: new axios.CancelToken(cancel => {
          // 存儲cancel函數(shù)
          this.pendingRequests.set(requestKey, { promise: requestPromise, cancel });
        })
      });

      const response = await requestPromise;
      return response.data;
    } catch (error) {
      if (!axios.isCancel(error)) {
        console.error('請求失敗:', error);
      }
      throw error;
    } finally {
      // 清理工作
      this.pendingRequests.delete(requestKey);
      if (showLoading && this.options.enableLoading) {
        this.setLoading(requestKey, false);
      }
    }
  }

  // 防抖請求
  debounceRequest(key, requestFn) {
    return new Promise((resolve, reject) => {
      // 清除已有定時器
      if (this.debounceTimers.has(key)) {
        clearTimeout(this.debounceTimers.get(key));
      }

      // 設置新定時器
      const timer = setTimeout(async () => {
        try {
          const result = await requestFn();
          resolve(result);
        } catch (error) {
          reject(error);
        } finally {
          this.debounceTimers.delete(key);
        }
      }, this.options.debounceTime);

      this.debounceTimers.set(key, timer);
    });
  }

  // 工具方法
  generateRequestKey(config) {
    const { url, method, params, data } = config;
    return `${method.toUpperCase()}:${url}:${JSON.stringify(params)}:${JSON.stringify(data)}`;
  }

  setLoading(key, isLoading) {
    this.loadingStates.set(key, isLoading);
    // 觸發(fā)loading狀態(tài)變化事件
    this.onLoadingChange(key, isLoading);
  }

  onLoadingChange(key, isLoading) {
    // 可以在這里實現(xiàn)loading UI更新
    console.log(`Loading狀態(tài)變化: ${key} -> ${isLoading}`);
  }

  // 取消請求
  cancelRequest(key) {
    if (this.pendingRequests.has(key)) {
      const { cancel } = this.pendingRequests.get(key);
      cancel(`手動取消請求: ${key}`);
      this.pendingRequests.delete(key);
    }
  }

  // 取消所有請求
  cancelAllRequests() {
    this.pendingRequests.forEach(({ cancel }, key) => {
      cancel(`取消所有請求: ${key}`);
    });
    this.pendingRequests.clear();
  }
}

三、使用建議和總結(jié)

3.1 方案選擇建議

3.2 最佳實踐總結(jié)

分層防御策略

  • UI層:按鈕loading和禁用狀態(tài)
  • 網(wǎng)絡層:請求攔截和取消
  • 業(yè)務層:防抖/節(jié)流控制

關鍵注意事項

  • 區(qū)分重復請求和并行請求的業(yè)務需求
  • 考慮請求失敗的重試機制
  • 注意內(nèi)存泄漏,及時清理請求緩存
  • 提供手動取消請求的接口

性能優(yōu)化建議

  • 對于相同請求,考慮使用Promise緩存
  • 合理設置防抖時間(通常300-500ms)
  • 監(jiān)控并限制最大并發(fā)請求數(shù)

3.3 總結(jié)

防止接口重復請求是一個系統(tǒng)工程,需要根據(jù)具體業(yè)務場景選擇合適的方案組合。建議:

  • 基礎防護:使用按鈕loading狀態(tài),這是最直接的用戶反饋
  • 核心防護:實現(xiàn)請求攔截和去重,這是技術(shù)保障
  • 增強防護:根據(jù)業(yè)務特點添加防抖/節(jié)流等優(yōu)化策略
  • 監(jiān)控反饋:添加請求日志,便于問題排查和優(yōu)化

在實際項目中,建議采用綜合分層方案,既保證用戶體驗,又確保系統(tǒng)穩(wěn)定性,同時為后續(xù)擴展留出空間。

以上就是前端防止接口重復請求的四種方案的詳細內(nèi)容,更多關于前端防止接口重復請求的資料請關注腳本之家其它相關文章!

相關文章

  • window.onbeforeunload方法在IE下無法正常工作的解決辦法

    window.onbeforeunload方法在IE下無法正常工作的解決辦法

    下面的代碼可以做到不管用戶是點擊了關閉,或者是在任務欄關閉、點擊后退、刷新、按F5鍵,都可以檢測到用戶即將離開的消息。
    2010-01-01
  • 5個讓你眼前一亮的JavaScript裝飾器技巧

    5個讓你眼前一亮的JavaScript裝飾器技巧

    JavaScript?裝飾器是一種特殊的功能,允許在不修改源代碼的情況下動態(tài)修改類和函數(shù)的行為,本文將介紹五個讓你眼前一亮的裝飾器技巧,包括裝飾函數(shù)參數(shù)、裝飾類屬性、裝飾函數(shù)返回值和裝飾函數(shù)調(diào)用,需要的朋友可以參考下
    2023-06-06
  • JS開發(fā)接入?deepseek?實現(xiàn)AI智能問診

    JS開發(fā)接入?deepseek?實現(xiàn)AI智能問診

    本文介紹了如何使用DeepSeek?API在UniApp項目中實現(xiàn)智能問診功能,代碼示例展示了如何構(gòu)建請求并處理響應,本文給大家介紹的非常詳細,感興趣的朋友一起看看吧
    2025-02-02
  • javascript 動態(tài)生成css代碼的兩種方法

    javascript 動態(tài)生成css代碼的兩種方法

    這篇文章主要介紹了javascript 動態(tài)生成css代碼的兩種方法,有時候我們需要利用js來動態(tài)生成頁面上style標簽中的css代碼,下面就給大家介紹兩種方法,需要的朋友可以參考下
    2017-03-03
  • tsconfig.json配置詳解

    tsconfig.json配置詳解

    這篇文章主要介紹了tsconfig.json配置詳解,tsconfig.json文件中指定了用來編譯這個項目的根文件和編譯選項,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-05-05
  • js實現(xiàn)符合國情的日期插件詳解

    js實現(xiàn)符合國情的日期插件詳解

    本篇文章主要介紹了js實現(xiàn)符合國情的日期插件的方法與步驟。具有一定的參考價值,下面跟著小編一起來看下吧
    2017-01-01
  • JavaScript箭頭函數(shù)與普通函數(shù)的區(qū)別示例詳解

    JavaScript箭頭函數(shù)與普通函數(shù)的區(qū)別示例詳解

    這篇文章主要為大家介紹了JavaScript箭頭函數(shù)與普通函數(shù)的區(qū)別示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-10-10
  • 使用JavaScript實現(xiàn)一個錄屏插件

    使用JavaScript實現(xiàn)一個錄屏插件

    不知道大家平時都是使用什么錄屏軟件呢,有沒有想過只用JavaScript我們也可以快速實現(xiàn)一個錄屏插件呢,感興趣的小伙伴就跟隨小編一起學習一下吧
    2024-10-10
  • 全面了解構(gòu)造函數(shù)繼承關鍵apply call

    全面了解構(gòu)造函數(shù)繼承關鍵apply call

    下面小編就為大家?guī)硪黄媪私鈽?gòu)造函數(shù)繼承關鍵apply call。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-07-07
  • JavaScript編程設計模式之構(gòu)造器模式實例分析

    JavaScript編程設計模式之構(gòu)造器模式實例分析

    這篇文章主要介紹了JavaScript編程設計模式之構(gòu)造器模式,簡單講述了構(gòu)造器模式的概念、原理,并結(jié)合實例形式分析了構(gòu)造器模式的定義與使用方法,需要的朋友可以參考下
    2017-10-10

最新評論

和平县| 馆陶县| 临汾市| 古田县| 麦盖提县| 家居| 拉孜县| 万年县| 淅川县| 安远县| 石狮市| 汉川市| 庆元县| 彩票| 从江县| 宜宾县| 沅陵县| 洪湖市| 本溪市| 内江市| 金门县| 云浮市| 汾阳市| 长沙县| 云龙县| 山丹县| 屯留县| 兴文县| 同德县| 赤峰市| 晋江市| 翁源县| 皋兰县| 顺平县| 进贤县| 永福县| 毕节市| 沾益县| 康马县| 黔江区| 衡阳县|