Skip to content

Commit

Permalink
feat(throttle): add handling of leading/trailing edges similar to lodash
Browse files Browse the repository at this point in the history
  • Loading branch information
Dema committed Apr 27, 2023
1 parent 786f745 commit 2e585a2
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 5 deletions.
53 changes: 48 additions & 5 deletions src/throttle/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,29 @@ type EventAsReturnType<Payload> = any extends Payload ? Event<Payload> : never;
export function throttle<T>(_: {
source: Unit<T>;
timeout: number | Store<number>;
leading?: boolean | Store<boolean>;
trailing?: boolean | Store<boolean>;
name?: string;
}): EventAsReturnType<T>;
export function throttle<T, Target extends Unit<T>>(_: {
source: Unit<T>;
timeout: number | Store<number>;
target: Target;
leading?: boolean | Store<boolean>;
trailing?: boolean | Store<boolean>;
name?: string;
}): Target;
export function throttle<T>({
source,
timeout,
leading,
trailing,
target = createEvent<T>(),
}: {
source: Unit<T>;
timeout: number | Store<number>;
leading?: boolean | Store<boolean>;
trailing?: boolean | Store<boolean>;
name?: string;
target?: Unit<any>;
}): EventAsReturnType<T> {
Expand All @@ -42,13 +50,18 @@ export function throttle<T>({
);

// It's ok - nothing will ever start unless source is triggered
const $payload = createStore<T>(null as unknown as T, { serialize: 'ignore' }).on(
source,
(_, payload) => payload,
);
const $payload = createStore<T>(null as unknown as T, {
serialize: 'ignore',
}).on(source, (_, payload) => payload);

const triggerTick = createEvent<T>();

const $leading = toStoreBoolean(leading, '$leading', false);
const $trailing = toStoreBoolean(trailing, '$trailing', true);

const $neverCalled = createStore(true).on(target, () => false);
const $lastCalled = createStore<number>(0).on(target, () => Date.now());

const $canTick = createStore(true, { serialize: 'ignore' })
.on(triggerTick, () => false)
.on(target, () => true);
Expand All @@ -66,8 +79,25 @@ export function throttle<T>({
});

sample({
source: $payload,
clock: $payload,
source: [$leading, $neverCalled],
filter: ([leading, neverCalled]) => leading && neverCalled,
target,
});

sample({
source: [$trailing, $payload] as const,
clock: timerFx.done,
filter: ([trailing]) => trailing,
fn: ([_, payload]) => payload,
target,
});
sample({
clock: $payload,
source: { trailing: $trailing, lastCalled: $lastCalled, timeout: $timeout },
filter: ({ trailing, lastCalled, timeout }) =>
!trailing && lastCalled + timeout < Date.now(),
fn: (_src, clk) => clk,
target,
});

Expand All @@ -88,3 +118,16 @@ function toStoreNumber(value: number | Store<number> | unknown): Store<number> {
`timeout parameter should be number or Store. "${typeof value}" was passed`,
);
}

function toStoreBoolean(
value: boolean | Store<boolean> | undefined,
name: string,
defaultValue: boolean,
): Store<boolean> {
if (is.store(value)) return value;
if (typeof value === 'boolean') {
return createStore(value, { name });
} else {
return createStore(defaultValue, { name });
}
}
10 changes: 10 additions & 0 deletions src/throttle/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ target = throttle({ source, timeout });

1. `source` ([_`Event`_] | [_`Store`_] | [_`Effect`_]) — Source unit, data from this unit used by the `target`
1. `timeout` ([_`number`_] | `Store<number>`) — time to wait before trigger `target` after last trigger or `source` trigger
1. `leading` ([_`boolean`_] | `Store<boolean>`) — trigger `target` on the leading edge of the `timeout`. If `true` first trigger of `source` causes
immediate trigger of `target`, default is `false`, `target` will be first triggered only after `timeout`
1. `trailing` ([_`boolean`_] | `Store<boolean>`) — trigger `target` on the trailing edge of the `timeout`. If `true` last trigger of `source`
withing `timeout` causes trigger of `target` after `timeout` expires. If `false` any trigger of `source`
will be ignored completely within the `timeout` not causing trigger of `target` after `timeout` expires.

### Returns

Expand Down Expand Up @@ -105,6 +110,11 @@ throttle({ source, timeout, target });
1. `source` ([_`Event`_] | [_`Store`_] | [_`Effect`_]) — Source unit, data from this unit used by the `target`
1. `timeout` ([_`number`_] | `Store<number>`) — time to wait before trigger `target` after last trigger or `source` trigger
1. `target` ([_`Event`_] | [_`Store`_] | [_`Effect`_]) — Target unit, that triggered each time after triggering `source` with argument from `source`
1. `leading` ([_`boolean`_] | `Store<boolean>`) — trigger `target` on the leading edge of the `timeout`. If `true` first trigger of `source` causes
immediate trigger of `target`, default is `false`, `target` will be first triggered only after `timeout`
1. `trailing` ([_`boolean`_] | `Store<boolean>`) — trigger `target` on the trailing edge of the `timeout`. If `true` last trigger of `source`
withing `timeout` causes trigger of `target` after `timeout` expires. If `false` any trigger of `source`
will be ignored completely within the `timeout` not causing trigger of `target` after `timeout` expires.

### Returns

Expand Down

0 comments on commit 2e585a2

Please sign in to comment.