generated at
throttle
実装
実行しなかったかどうかが、 fn の引数から読み取れるようにしてある
逆に、 fn から state の情報を返さない限り、 ThrottledFn の使用側では間引かれたかどうかがわからない
中断処理の実装は throttle 呼び出し側に一任されている
Promise.try()の使い道がわかったtakker
同期的に投げた例外をPromiseに変換する際に便利

JSRに公開したい
deno-async-throttleのrepoを使おうかな
そもそも、deno-async-throttleで実装したthrottleはthrottleではなくdebounceに近いものであった
throttleは一定間隔で1度しか実行しない手法
debounceは呼び出し終了から一定時間たったら実行する手法
deno-async-throttleで実装していたのは後者
14:20:37 deno-async-throttleのrepoをこのコードに書き換えた

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; }; };

#2024-12-20 14:20:56
#2024-12-19 21:48:41