// src/utils.ts import { getIsLowMemory } from "./memory.js"; // AnyFunction は内部型のみ。外部引数は T extends (...args: never[]) => unknown で表現 type AnyFunction = (...args: never[]) => unknown; interface DebounceOptions { delay?: number; leading?: boolean; trailing?: boolean; usePromise?: boolean; } export function debounce( fn: T, options: DebounceOptions | number = {} ) { const opts: DebounceOptions = typeof options === "number" ? { delay: options } : options; const { delay = 300, leading = false, trailing = true, usePromise = true, } = opts; let timer: ReturnType | undefined; let lastArgs: Parameters | undefined; let pending: | { resolve: (v: ReturnType) => void; reject: (e: unknown) => void } | undefined; const getDelay = (): number => (getIsLowMemory() ? delay * 2 : delay); const execute = async (): Promise> => { if (!lastArgs) return undefined as ReturnType; try { const result = await (fn as (...a: Parameters) => ReturnType)(...lastArgs); pending?.resolve(result); return result; } catch (e) { pending?.reject(e); throw e; } finally { pending = undefined; } }; const debounced = (...args: Parameters): Promise> | void => { lastArgs = args; const shouldCallNow = leading && !timer; if (timer) clearTimeout(timer); if (shouldCallNow) { if (usePromise) { return Promise.resolve( (fn as (...a: Parameters) => ReturnType)(...args) ); } (fn as (...a: Parameters) => unknown)(...args); return; } if (!trailing) return; if (usePromise) { return new Promise>((resolve, reject) => { pending?.reject(new Error("debounced_cancelled")); pending = { resolve, reject }; timer = setTimeout(() => { timer = undefined; void execute(); }, getDelay()); }); } timer = setTimeout(() => { timer = undefined; void (fn as (...a: Parameters) => unknown)(...(lastArgs as Parameters)); }, getDelay()); }; debounced.cancel = (): void => { if (timer) { clearTimeout(timer); timer = undefined; } pending?.reject(new Error("debounced_cancelled")); pending = undefined; }; debounced.flush = (): Promise> | undefined => { if (!timer) return; clearTimeout(timer); timer = undefined; return execute(); }; return debounced as typeof debounced & { cancel: () => void; flush: () => Promise> | undefined; }; }