前端请求处理增加版

针对上一篇代码中的潜在问题或可以改进的地方。可能的方向包括:

  1. 缓存键生成 ‌:当前的getKey方法使用JSON.stringify处理参数和data,但可能存在对象顺序不同导致相同请求生成不同键的问题。例如,{a:1, b:2}{b:2, a:1}会被序列化成不同的字符串,但实际上请求是相同的。这需要优化,确保键的唯一性更准确。
  2. 内存管理‌:虽然现在有TTL,但如果有大量请求,可能导致内存占用过高。可以考虑引入LRU(最近最少使用)策略,限制缓存条目数量,自动淘汰旧条目。
  3. 请求竞速处理‌:当多个相同请求同时发起时,如果前一个请求还未完成,新的请求应该复用同一个Promise。当前的实现已经处理了这一点,但需要考虑在缓存过期后,如何正确处理新的请求,避免重复。
  4. 错误处理‌:当前的代码在请求失败后会删除缓存,但可能需要更细致的错误处理策略,比如某些错误(如网络错误)可能需要更早的重试,或者保留缓存一段时间以避免重复请求。
  5. 并发请求处理‌:当多个相同的请求同时发起时,是否会出现多次实际请求?需要确保即使在高并发下,同一个请求只发送一次。
  6. 缓存持久化‌:当前缓存是内存中的,页面刷新后会丢失。是否可以考虑结合本地存储(如localStorage)来持久化某些缓存数据?
  7. 自动刷新机制‌:类似SWR的stale-while-revalidate策略,可以在缓存过期后自动重新请求数据,同时先返回旧数据,提升用户体验。
  8. 调试和监控‌:添加日志或监控接口,方便开发者查看缓存状态,调试问题。
  9. 类型定义‌:如果使用TypeScript,可以添加类型声明,提高代码的健壮性和可维护性。
  10. 取消请求‌:支持取消请求,避免无效请求继续处理,节省资源。

接下来,我需要考虑用户可能的实际使用场景。用户可能是在开发一个中大型前端应用,遇到重复请求的问题,希望通过缓存优化性能。用户可能对缓存机制有一定了解,但希望有一个更健壮、更高效的解决方案。

用户可能没有考虑到的点包括内存泄漏、缓存键的准确性、并发请求的处理细节以及更高级的缓存策略如LRU。此外,错误处理和调试支持也是实际开发中容易忽视的部分。

基于这些分析,我需要给出具体的优化建议,每个建议都要有代码示例和解释,确保用户能够理解和实施。同时,要指出每个优化点的必要性和适用场景,帮助用户根据实际情况选择改进方向。

以下是针对该 RequestPool 类的优化建议和具体实现方案:


一、缓存键生成优化

问题

当前 JSON.stringify(params) 存在对象属性顺序敏感问题:

css 复制代码
// 参数顺序不同会导致生成不同缓存键
{ a: 1, b: 2 } => "a=1&b=2"
{ b: 2, a: 1 } => "b=2&a=1" 

优化方案

javascript 复制代码
import { stringify } from 'qs'; // 或自定义稳定序列化方法

getKey(config) {
  const { method, url, params, data } = config;
  const sortedParams = stringify(params, { sort: (a, b) => a.localeCompare(b) });
  const sortedData = typeof data === 'object' 
    ? stringify(data, { sort: (a, b) => a.localeCompare(b) }) 
    : data;
  return `${method}-${url}-${sortedParams}-${sortedData}`;
}

二、内存管理增强

1. LRU缓存淘汰策略

kotlin 复制代码
constructor(maxSize = 100) {
  this.pool = new Map();
  this.maxSize = maxSize; // 新增最大缓存数量限制
}

// 在 set 前检查缓存数量
set(key, value) {
  if (this.pool.size >= this.maxSize) {
    const oldestKey = this.pool.keys().next().value;
    this.pool.delete(oldestKey);
  }
  this.pool.set(key, value);
}

2. 定期清理过期缓存

javascript 复制代码
constructor() {
  // 增加定时清理器
  this.cleanupInterval = setInterval(() => {
    const now = Date.now();
    this.pool.forEach((value, key) => {
      if (value.expire < now) this.pool.delete(key);
    });
  }, 60000); // 每分钟清理一次
}

// 销毁时清除定时器
destroy() {
  clearInterval(this.cleanupInterval);
}

三、请求控制强化

1. 请求取消支持

arduino 复制代码
import axios from 'axios';

async request(config) {
  const source = axios.CancelToken.source();
  const enhancedConfig = {
    ...config,
    cancelToken: source.token
  };

  const promise = axios(enhancedConfig)
    .finally(() => { /* ... */ });

  // 保存取消方法
  promise.cancel = () => {
    source.cancel('Request canceled by cache system');
    this.pool.delete(key);
  };

  return promise;
}

2. 请求重试机制

arduino 复制代码
const promise = axios(config)
  .catch(async (error) => {
    if (config.retryCount > 0 && isRetryableError(error)) {
      config.retryCount--;
      return this.request(config);
    }
    throw error;
  });

四、功能扩展

1. 本地持久化缓存

javascript 复制代码
// 存储层抽象
class CacheStorage {
  setItem(key, value) {
    localStorage.setItem(key, JSON.stringify({
      expire: value.expire,
      data: value.data
    }));
  }
}

// 使用示例
async request(config) {
  if (config.usePersistent) {
    const cached = localStorage.getItem(key);
    if (cached && cached.expire > Date.now()) {
      return Promise.resolve(cached.data);
    }
  }
  // ...原有逻辑
}

2. 自动刷新策略

arduino 复制代码
const promise = axios(config).then(response => {
  if (config.autoRefresh) {
    setTimeout(() => {
      this.clearCache(key);
      this.request(config); 
    }, config.refreshInterval);
  }
  return response;
});

五、调试能力增强

1. 缓存监控方法

javascript 复制代码
getCacheStats() {
  return {
    total: this.pool.size,
    keys: Array.from(this.pool.keys()),
    memoryUsage: estimateMemoryUsage(this.pool)
  };
}

// 内存估算函数
function estimateMemoryUsage(map) {
  let size = 0;
  map.forEach((value, key) => {
    size += key.length * 2; // UTF-16 编码
    size += JSON.stringify(value).length * 2;
  });
  return size + ' KB';
}

2. 请求追踪日志

javascript 复制代码
async request(config) {
  const requestId = generateUUID();
  console.debug(`[${requestId}] Init request:`, config);

  const promise = axios(config)
    .then(response => {
      console.debug(`[${requestId}] Success:`, response);
      return response;
    })
    .catch(error => {
      console.debug(`[${requestId}] Failed:`, error);
      throw error;
    });

  // ...其余逻辑
}

六、安全增强

1. 缓存加密支持

javascript 复制代码
import CryptoJS from 'crypto-js';

getKey(config) {
  const rawKey = `${method}-${url}-${sortedParams}-${sortedData}`;
  return CryptoJS.SHA256(rawKey).toString();
}

2. 防DDOS保护

javascript 复制代码
constructor() {
  this.requestCounts = new Map();
}

async request(config) {
  const clientIP = getClientIP(); // 需要服务端支持
  if (this.requestCounts.get(clientIP) > 1000) {
    throw new Error('Request limit exceeded');
  }
  // ...原有逻辑
}

最终优化版代码

kotlin 复制代码
import axios from 'axios';
import { stringify } from 'qs';
import CryptoJS from 'crypto-js';

class AdvancedRequestPool {
  constructor(options = {}) {
    this.pool = new Map();
    this.defaultTTL = options.defaultTTL || 5000;
    this.maxSize = options.maxSize || 100;
    this.encryptKey = options.encryptKey || null;
    
    // 启动定时清理
    this.cleanupInterval = setInterval(this.cleanup.bind(this), 60000);
  }

  generateKey(config) {
    const { method, url, params, data } = config;
    const sortedParams = stringify(params, { sort: (a, b) => a.localeCompare(b) });
    const sortedData = typeof data === 'object' 
      ? stringify(data, { sort: (a, b) => a.localeCompare(b) }) 
      : data;
    const rawKey = `${method}-${url}-${sortedParams}-${sortedData}`;
    
    return this.encryptKey 
      ? CryptoJS.HmacSHA256(rawKey, this.encryptKey).toString()
      : rawKey;
  }

  async request(config) {
    const key = this.generateKey(config);
    const now = Date.now();

    // 存在有效缓存
    if (this.pool.has(key)) {
      const entry = this.pool.get(key);
      if (entry.expire > now) return entry.promise;
      entry.promise.cancel?.(); // 取消过期请求
    }

    // 创建新请求
    const source = axios.CancelToken.source();
    const finalConfig = {
      ...config,
      cancelToken: source.token
    };

    const promise = axios(finalConfig)
      .then(response => {
        if (config.autoRefresh) {
          this.scheduleRefresh(key, config);
        }
        return response;
      })
      .catch(error => {
        if (config.retryCount > 0) {
          return this.handleRetry(key, config, error);
        }
        throw error;
      })
      .finally(() => {
        if (!config.keepAlive) this.pool.delete(key);
      });

    // 添加取消方法
    promise.cancel = () => {
      source.cancel('Request canceled by cache system');
      this.pool.delete(key);
    };

    // 控制缓存数量
    if (this.pool.size >= this.maxSize) {
      const oldestKey = this.pool.keys().next().value;
      this.pool.get(oldestKey)?.promise.cancel?.();
      this.pool.delete(oldestKey);
    }

    this.pool.set(key, {
      promise,
      expire: Date.now() + (config.cacheTTL || this.defaultTTL),
      config
    });

    return promise;
  }

  cleanup() {
    const now = Date.now();
    this.pool.forEach((value, key) => {
      if (value.expire < now) {
        value.promise.cancel?.();
        this.pool.delete(key);
      }
    });
  }

  scheduleRefresh(key, config) {
    setTimeout(() => {
      if (this.pool.has(key)) {
        this.pool.get(key).promise.cancel?.();
        this.request(config);
      }
    }, config.refreshInterval || 30000);
  }

  async handleRetry(key, originalConfig, error) {
    if (!isRetryableError(error)) throw error;
    
    originalConfig.retryCount--;
    return new Promise(resolve => {
      setTimeout(async () => {
        const retryPromise = this.request(originalConfig);
        this.pool.set(key, { 
          ...this.pool.get(key),
          promise: retryPromise
        });
        resolve(await retryPromise);
      }, originalConfig.retryDelay || 1000);
    });
  }

  destroy() {
    clearInterval(this.cleanupInterval);
    this.pool.forEach(value => value.promise.cancel?.());
    this.pool.clear();
  }
}

优化点总结

优化方向 具体实现 解决的问题
缓存键稳定性 使用排序后的参数序列化 避免相同请求因参数顺序不同导致缓存失效
内存控制 LRU策略+定期清理 防止内存泄漏和溢出
请求生命周期管理 取消请求+自动刷新 提升资源利用率和数据实时性
错误弹性 可配置的重试机制 增强网络波动时的健壮性
安全防护 HMAC加密+请求限流 防止恶意攻击和滥用
可观测性 缓存统计+请求追踪 提升调试和维护效率

建议根据实际项目需求选择性地实现这些优化,对于中小型项目基础版本已足够,大型复杂系统建议逐步引入高级功能。

相关推荐
xjt_0901几秒前
浅析Web存储系统
前端
foxhuli22938 分钟前
禁止ifrmare标签上的文件,实现自动下载功能,并且隐藏工具栏
前端
青皮桔1 小时前
CSS实现百分比水柱图
前端·css
失落的多巴胺1 小时前
使用deepseek制作“喝什么奶茶”随机抽签小网页
javascript·css·css3·html5
DataGear1 小时前
如何在DataGear 5.4.1 中快速制作SQL服务端分页的数据表格看板
javascript·数据库·sql·信息可视化·数据分析·echarts·数据可视化
影子信息1 小时前
vue 前端动态导入文件 import.meta.glob
前端·javascript·vue.js
青阳流月1 小时前
1.vue权衡的艺术
前端·vue.js·开源
样子20181 小时前
Vue3 之dialog弹框简单制作
前端·javascript·vue.js·前端框架·ecmascript
kevin_水滴石穿1 小时前
Vue 中报错 TypeError: crypto$2.getRandomValues is not a function
前端·javascript·vue.js
翻滚吧键盘1 小时前
vue文本插值
javascript·vue.js·ecmascript