remeda
Version:
A utility library for JavaScript and Typescript.
150 lines (149 loc) • 6.87 kB
TypeScript
import { RequireAtLeastOne } from "type-fest";
//#region src/funnel.d.ts
type FunnelOptions<Args extends RestArguments, R> = {
readonly reducer?: (accumulator: R | undefined, ...params: Args) => R;
} & FunnelTimingOptions;
type FunnelTimingOptions = ({
readonly triggerAt?: "end";
} & (({
readonly minGapMs: number;
} & RequireAtLeastOne<{
readonly minQuietPeriodMs: number;
readonly maxBurstDurationMs: number;
}>) | {
readonly minQuietPeriodMs?: number;
readonly maxBurstDurationMs?: number;
readonly minGapMs?: never;
})) | {
readonly triggerAt: "start" | "both";
readonly minQuietPeriodMs?: number;
readonly maxBurstDurationMs?: number;
readonly minGapMs?: number;
};
type RestArguments = Array<any>;
type Funnel<Args extends RestArguments = []> = {
/**
* Call the function. This might result in the `execute` function being called
* now or later, depending on it's configuration and it's current state.
*
* @param args - The args are defined by the `reducer` function.
*/
readonly call: (...args: Args) => void;
/**
* Resets the funnel to it's initial state. Any calls made since the last
* invocation will be discarded.
*/
readonly cancel: () => void;
/**
* Triggers an invocation regardless of the current state of the funnel.
* Like any other invocation, The funnel will also be reset to it's initial
* state afterwards.
*/
readonly flush: () => void;
/**
* The funnel is in it's initial state (there are no active timeouts).
*/
readonly isIdle: boolean;
};
/**
* Creates a funnel that controls the timing and execution of `callback`. Its
* main purpose is to manage multiple consecutive (usually fast-paced) calls,
* reshaping them according to a defined batching strategy and timing policy.
* This is useful when handling uncontrolled call rates, such as DOM events or
* network traffic. It can implement strategies like debouncing, throttling,
* batching, and more.
*
* An optional `reducer` function can be provided to allow passing data to the
* callback via calls to `call` (otherwise the signature of `call` takes no
* arguments).
*
* Typing is inferred from `callback`s param, and from the rest params that
* the optional `reducer` function accepts. Use **explicit** types for these
* to ensure that everything _else_ is well-typed.
*
* Notice that this function constructs a funnel **object**, and does **not**
* execute anything when called. The returned object should be used to execute
* the funnel via the its `call` method.
*
* - Debouncing: use `minQuietPeriodMs` and any `triggerAt`.
* - Throttling: use `minGapMs` and `triggerAt: "start"` or `"both"`.
* - Batching: See the reference implementation in [`funnel.reference-batch.test.ts`](https://github.com/remeda/remeda/blob/main/packages/remeda/src/funnel.reference-batch.test.ts).
*
* @param callback - The main function that would be invoked periodically based
* on `options`. The function would take the latest result of the `reducer`; if
* no calls where made since the last time it was invoked it will not be
* invoked. (If a return value is needed, it should be passed via a reference or
* via closure to the outer scope of the funnel).
* @param options - An object that defines when `execute` should be invoked,
* relative to the calls of `call`. An empty/missing options object is
* equivalent to setting `minQuietPeriodMs` to `0`.
* @param options.reducer - Combines the arguments passed to `call` with the
* value computed on the previous call (or `undefined` on the first time). The
* goal of the function is to extract and summarize the data needed for
* `callback`. It should be fast and simple as it is called often and should
* defer heavy operations to the `execute` function. If the final value
* is `undefined`, `callback` will not be called.
* @param options.triggerAt - At what "edges" of the funnel's burst window
* would `execute` invoke:
* - `start` - the function will be invoked immediately (within the **same**
* execution frame!), and any subsequent calls would be ignored until the funnel
* is idle again. During this period `reducer` will also not be called.
* - `end` - the function will **not** be invoked initially but the timer will
* be started. Any calls during this time would be passed to the reducer, and
* when the timers are done, the reduced result would trigger an invocation.
* - `both` - the function will be invoked immediately, and then the funnel
* would behave as if it was in the 'end' state. @default 'end'.
* @param options.minQuietPeriodMs - The burst timer prevents subsequent calls
* in short succession to cause excessive invocations (aka "debounce"). This
* duration represents the **minimum** amount of time that needs to pass
* between calls (the "quiet" part) in order for the subsequent call to **not**
* be considered part of the burst. In other words, as long as calls are faster
* than this, they are considered part of the burst and the burst is extended.
* @param options.maxBurstDurationMs - Bursts are extended every time a call is
* made within the burst period. This means that the burst period could be
* extended indefinitely. To prevent such cases, a maximum burst duration could
* be defined. When `minQuietPeriodMs` is not defined and this option is, they
* will both share the same value.
* @param options.minGapMs - A minimum duration between calls of `execute`.
* This is maintained regardless of the shape of the burst and is ensured even
* if the `maxBurstDurationMs` is reached before it. (aka "throttle").
* @returns A funnel with a `call` function that is used to trigger invocations.
* In addition to it the funnel also comes with the following functions and
* properties:
* - `cancel` - Resets the funnel to it's initial state, discarding the current
* `reducer` result without calling `execute` on it.
* - `flush` - Triggers an invocation even if there are active timeouts, and
* then resets the funnel to it's initial state.
* - `isIdle` - Checks if there are any active timeouts.
* @signature
* R.funnel(callback, options);
* @example
* const debouncer = R.funnel(
* () => {
* console.log("Callback executed!");
* },
* { minQuietPeriodMs: 100 },
* );
* debouncer.call();
* debouncer.call();
*
* const throttle = R.funnel(
* () => {
* console.log("Callback executed!");
* },
* { minGapMs: 100, triggerAt: "start" },
* );
* throttle.call();
* throttle.call();
* @category Function
*/
declare function funnel<Args extends RestArguments = [], R = never>(callback: (data: R) => void, {
triggerAt,
minQuietPeriodMs,
maxBurstDurationMs,
minGapMs,
reducer
}: FunnelOptions<Args, R>): Funnel<Args>;
//#endregion
export { funnel };
//# sourceMappingURL=funnel-BApLGnfE.d.ts.map