/** * Creates a function that samples calls at regular intervals and captures trailing calls. * - Drops calls that occur between sampling intervals * - Takes one call per sampling interval if available * - Captures the last call if no call was made during the interval * * @param fn The function to sample * @param sampleInterval How often to sample calls (in ms) * @returns The sampled function */ export function createSampler any>(fn: T, sampleInterval: number): T { let lastArgs: Parameters | null = null; let lastTime = 0; let timeout: NodeJS.Timeout | null = null; // Create a function with the same type as the input function const sampled = function (this: any, ...args: Parameters) { const now = Date.now(); lastArgs = args; // If we're within the sample interval, just store the args if (now - lastTime < sampleInterval) { // Set up trailing call if not already set if (!timeout) { timeout = setTimeout( () => { timeout = null; lastTime = Date.now(); if (lastArgs) { fn.apply(this, lastArgs); lastArgs = null; } }, sampleInterval - (now - lastTime), ); } return undefined as unknown as ReturnType; } // If we're outside the interval, execute immediately lastTime = now; const result = fn.apply(this, args); lastArgs = null; return result; } as unknown as T; return sampled; } /** * 创建一个采样异步函数,在指定的时间间隔内只执行一次,并捕获尾随调用。 * - 在采样间隔内的调用会被丢弃,但会保存最后一次调用的参数 * - 每个采样间隔只执行一次调用 * - 如果在间隔内没有调用,则捕获最后一次调用 * - 始终返回一个 Promise * - 添加了执行状态管理,防止在高负载情况下连续多次执行 * * @param fn 要采样的异步函数 * @param sampleInterval 采样间隔(毫秒) * @param options 配置选项 * @returns 采样后的异步函数 */ export function createAsyncSampler Promise>(fn: T, sampleInterval: number): T { // 初始化状态变量,确保有合理的默认值 const now = Date.now(); let lastArgs: Parameters | null = null; let timeout: NodeJS.Timeout | null = null; let lastThis: any = null; let isExecuting = false; // 执行状态标志 let nextExecutionTime = now + sampleInterval; // 初始化为当前时间 + 采样间隔 let pendingPromise: Promise | null = null; // 当前正在执行的 Promise // 清除所有定时器 const clearAllTimeouts = () => { if (timeout) { clearTimeout(timeout); timeout = null; } }; // 安全地重置执行状态 const resetExecutionState = () => { isExecuting = false; pendingPromise = null; }; // 创建与输入函数类型相同的函数 const sampled = function (this: any, ...args: Parameters) { const now = Date.now(); lastArgs = args; lastThis = this; // 检查是否可以执行 // 1. 当前没有正在执行的任务 // 2. 当前时间已经超过了下次允许执行的时间 // 3. 没有待处理的 Promise const canExecuteNow = !isExecuting && now >= nextExecutionTime && !pendingPromise; if (!canExecuteNow) { const waitTime = Math.max(0, nextExecutionTime - now); // 清除之前的定时器,确保只有一个定时器在运行 clearAllTimeouts(); // 设置新的定时器,延迟执行 // 使用 Math.max 确保至少等待 100ms,避免过于频繁的检查 const delayTime = Math.max(100, Math.min(sampleInterval, waitTime)); timeout = setTimeout(() => { const currentTime = Date.now(); // 再次检查是否可以执行 if (!isExecuting && currentTime >= nextExecutionTime && !pendingPromise) { clearAllTimeouts(); return executeTask(); } }, delayTime); // 返回一个空的 Promise,以支持链式调用 return Promise.resolve() as any as ReturnType; } return executeTask(); }; async function executeTask(): Promise { isExecuting = true; try { if (!lastArgs) { resetExecutionState(); return Promise.resolve(); } const result = fn.apply(lastThis, lastArgs); lastArgs = null; pendingPromise = result; return result.finally(() => { // 更新下一次执行时间 nextExecutionTime = Date.now() + sampleInterval; resetExecutionState(); }); } catch (error) { // 即使发生错误也更新下一次执行时间 nextExecutionTime = Date.now() + sampleInterval; resetExecutionState(); return Promise.reject(error); } } return sampled as unknown as T; }