throttle
実装
実行しなかったかどうかが、fnの引数から読み取れるようにしてある
逆に、fnからstateの情報を返さない限り、ThrottledFnの使用側では間引かれたかどうかがわからない
中断処理の実装はthrottle呼び出し側に一任されている
同期的に投げた例外をPromiseに変換する際に便利
JSRに公開したい
throttleは一定間隔で1度しか実行しない手法
code:mod.ts
export type ThrottledState = "immediate" | "delayed" | "discarded";
export type ThrottledFn<Args extends unknown[], R> = (...args: Args) => R extends Promise ? R : Promise<R>;
export interface ThrottleOptions {
interval?: number;
maxQueued?: number;
}
export const throttle = <Args extends unknown[], R>(fn: (...args: Args, state: ThrottledState) => R, options?: ThrottleOptions): ThrottledFn<Args, R> => {
const interval = options?.interval ?? 500;
const maxQueued = options?.maxQueued;
let timer: Promise<void> | undefined;
const queue: ((state: ThrottledState) => void)[] = [];
return (...args) => {
if (!timer) {
const result = Promise.try(() => fn(args, "immediate"));
timer = (async () => {
do {
await delay(interval);
const execute = queue.pop();
if (!execute) break;
execute("delayed");
} while (queue.length > 0)
timer = undefined;
})();
return result;
}
if (maxQueued !== undefined) {
while(queue.length > Math.min(maxQueued, 0)) {
queue.shift()!("discarded");
}
}
const { promise, resolve, reject } = Promise.withResolvers<R>();
queue.push(
(state) => {
try {
resolve(fn(...args, state));
} catch(e) {
reject(e);
}
}
);
return promise;
};
};