import { sleep } from "./sleep.ts"; /** Options for `throttle` */ export interface Options { /** If it's set to `true` and queue isn't empty, the function is executed again at the end. * * default: `false` */ trailing?: boolean; interval?: number; } /** Result of `throttle` */ export interface Result { /** whether the provided function is executed */ executed: boolean; /** the result of the provided function * * If the function is not executed, it is set to `undefined` */ result?: U; } type Resolve = (_value: T | PromiseLike) => void; type Reject = (reason?: unknown) => void; type Queue = { parameters: T; resolve: Resolve>; reject: Reject; }; /** Make the async function execute only at a time * * @param callback the async function making execute only at a time * @param options options */ export function throttle( callback: (..._args: T) => Promise, options?: Options, ): (...parameters: T) => Promise> { const { trailing = false, interval = 0 } = options ?? {}; let queue: Queue | undefined; let running = false; const push = (data?: Queue) => { queue?.resolve?.({ executed: false }); queue = data; }; const pop = (): Queue => { const { ...data } = queue; queue = undefined; return data; }; const runNext = async () => { if (running || !queue) { return; } running = true; if (interval > 0) { await sleep(interval); } const { parameters, resolve, reject } = pop(); try { const result = await callback(...parameters); running = false; resolve({ result, executed: true }); } catch (e) { running = false; reject(e); } finally { if (trailing) { await runNext(); } else { push(); await Promise.resolve(); } } }; return (...parameters: T) => new Promise>((resolve, reject) => { push({ parameters, resolve, reject }); runNext(); }); }