针对上一篇代码中的潜在问题或可以改进的地方。可能的方向包括:
- 缓存键生成 :当前的
getKey
方法使用JSON.stringify
处理参数和data,但可能存在对象顺序不同导致相同请求生成不同键的问题。例如,{a:1, b:2}
和{b:2, a:1}
会被序列化成不同的字符串,但实际上请求是相同的。这需要优化,确保键的唯一性更准确。 - 内存管理:虽然现在有TTL,但如果有大量请求,可能导致内存占用过高。可以考虑引入LRU(最近最少使用)策略,限制缓存条目数量,自动淘汰旧条目。
- 请求竞速处理:当多个相同请求同时发起时,如果前一个请求还未完成,新的请求应该复用同一个Promise。当前的实现已经处理了这一点,但需要考虑在缓存过期后,如何正确处理新的请求,避免重复。
- 错误处理:当前的代码在请求失败后会删除缓存,但可能需要更细致的错误处理策略,比如某些错误(如网络错误)可能需要更早的重试,或者保留缓存一段时间以避免重复请求。
- 并发请求处理:当多个相同的请求同时发起时,是否会出现多次实际请求?需要确保即使在高并发下,同一个请求只发送一次。
- 缓存持久化:当前缓存是内存中的,页面刷新后会丢失。是否可以考虑结合本地存储(如localStorage)来持久化某些缓存数据?
- 自动刷新机制:类似SWR的stale-while-revalidate策略,可以在缓存过期后自动重新请求数据,同时先返回旧数据,提升用户体验。
- 调试和监控:添加日志或监控接口,方便开发者查看缓存状态,调试问题。
- 类型定义:如果使用TypeScript,可以添加类型声明,提高代码的健壮性和可维护性。
- 取消请求:支持取消请求,避免无效请求继续处理,节省资源。
接下来,我需要考虑用户可能的实际使用场景。用户可能是在开发一个中大型前端应用,遇到重复请求的问题,希望通过缓存优化性能。用户可能对缓存机制有一定了解,但希望有一个更健壮、更高效的解决方案。
用户可能没有考虑到的点包括内存泄漏、缓存键的准确性、并发请求的处理细节以及更高级的缓存策略如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加密+请求限流 | 防止恶意攻击和滥用 |
可观测性 | 缓存统计+请求追踪 | 提升调试和维护效率 |
建议根据实际项目需求选择性地实现这些优化,对于中小型项目基础版本已足够,大型复杂系统建议逐步引入高级功能。