@mt-kit/utils
Version:
96 lines • 4.05 kB
JavaScript
const DEFAULT_CACHE_WINDOW = 500; // 500ms 内视为同一批次请求
const MIN_CACHE_WINDOW = 0; // 最小时间窗口
const MAX_CACHE_WINDOW = 60000; // 最大时间窗口(60秒)
/**
* 全局缓存 Map:以函数引用为 key,存储每个函数的缓存状态
* 这样即使在不同组件中多次调用 createDedupedRequest(dataList),
* 只要传入的是同一个函数引用,它们就会共享同一个缓存状态
*
* 使用 Map 而不是 WeakMap,以便在请求完成后可以手动清除缓存
*/
const cacheMap = new Map();
/**
* 验证并规范化时间窗口参数
*/
function normalizeCacheWindow(cacheWindow) {
// 如果时间窗口不是数字,或者是一个 NaN,则使用默认值
if (typeof cacheWindow !== "number" || Number.isNaN(cacheWindow)) {
console.warn(`[createDedupedRequest] 无效的时间窗口值: ${cacheWindow},使用默认值 ${DEFAULT_CACHE_WINDOW}ms`);
return DEFAULT_CACHE_WINDOW;
}
// 如果时间窗口小于最小值,则使用最小值
if (cacheWindow < MIN_CACHE_WINDOW) {
console.warn(`[createDedupedRequest] 时间窗口 ${cacheWindow}ms 小于最小值 ${MIN_CACHE_WINDOW}ms,使用最小值`);
return MIN_CACHE_WINDOW;
}
// 如果时间窗口大于最大值,则使用最大值
if (cacheWindow > MAX_CACHE_WINDOW) {
console.warn(`[createDedupedRequest] 时间窗口 ${cacheWindow}ms 大于最大值 ${MAX_CACHE_WINDOW}ms,使用最大值`);
return MAX_CACHE_WINDOW;
}
return Math.floor(cacheWindow);
}
/**
* 安全地清理缓存状态
*/
function safeCleanupCache(fn) {
try {
cacheMap.delete(fn);
}
catch (error) {
console.error("[createDedupedRequest] 清理缓存时发生错误:", error);
}
}
/**
* 创建带请求去重功能的函数包装器
*
* 在指定时间窗口内,多次调用同一个函数时:
* - 只有第一次会真正向后端请求
* - 其它调用会等待第一次请求完成,并拿到相同结果
* - 请求完成后会清除缓存,确保下次调用时重新请求
*
* 这里不关心具体请求逻辑,由外部传入真正的请求函数(如 dataList)。
* 基于函数引用作为 key 进行全局缓存管理,所以可以在组件内部使用。
*
* @param fn - 需要包装的异步函数
* @param cacheWindow - 时间窗口(毫秒),默认 500ms,范围 [0, 60000]
* @returns 带去重功能的包装函数
* @throws 如果 fn 不是函数,会抛出错误
*/
export default function createDedupedRequest(fn, cacheWindow = DEFAULT_CACHE_WINDOW) {
// 参数验证:确保 fn 是函数
if (typeof fn !== "function") {
throw new TypeError(`[createDedupedRequest] 参数 fn 必须是函数,但收到: ${typeof fn}`);
}
// 规范化时间窗口
const normalizedCacheWindow = normalizeCacheWindow(cacheWindow);
// 获取或创建该函数的缓存状态
let cacheState = cacheMap.get(fn);
if (!cacheState) {
cacheState = {
lastResult: undefined,
lastRequestTime: 0,
pendingPromise: null
};
cacheMap.set(fn, cacheState);
}
return (...args) => {
const now = Date.now();
// 仍在时间窗口内,并且已有进行中的请求,直接复用该 Promise
if (cacheState.pendingPromise && now - cacheState.lastRequestTime <= normalizedCacheWindow) {
return cacheState.pendingPromise;
}
// 发起新请求
cacheState.lastRequestTime = now;
// 创建请求 Promise,并处理成功和失败情况
cacheState.pendingPromise = Promise.resolve().then(() => fn(...args)).then(res => res, error => {
// 请求失败时,确保错误能够正确传播
throw error;
}).finally(() => {
// 无论成功还是失败,都要清理缓存
safeCleanupCache(fn);
});
return cacheState.pendingPromise;
};
}
//# sourceMappingURL=index.js.map