Skip to content

Commit

Permalink
renaming falling to trailing, properly handle throttling after tr…
Browse files Browse the repository at this point in the history
…ailing call, expand test
  • Loading branch information
crishoj committed Jul 21, 2024
1 parent 3c26e54 commit 745942a
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 22 deletions.
41 changes: 23 additions & 18 deletions src/curry/throttle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,29 +26,34 @@ export type ThrottledFunction<TArgs extends any[]> = {
* ```
*/
export function throttle<TArgs extends any[]>(
{ interval, falling = false }: { interval: number; falling?: boolean },
{ interval, trailing = false }: { interval: number; trailing?: boolean },
func: (...args: TArgs) => any,
): ThrottledFunction<TArgs> {
let ready = true
let pending = false
let timer: unknown = undefined
let lastCallTime: number | undefined = undefined
let timer: ReturnType<typeof setTimeout> | undefined = undefined
let lastArgs: TArgs | undefined = undefined

const throttled: ThrottledFunction<TArgs> = (...args: TArgs) => {
pending = true
if (!ready) {
return
const currentTime = Date.now()

if (lastCallTime === undefined || currentTime - lastCallTime >= interval) {
func(...args)
lastCallTime = currentTime
lastArgs = undefined
} else {
lastArgs = args
}

if (timer === undefined) {
timer = setTimeout(() => {
timer = undefined
if (trailing && lastArgs) {
func(...lastArgs)
lastCallTime = Date.now()
lastArgs = undefined
}
}, interval)
}
func(...args)
ready = false
pending = false
timer = setTimeout(() => {
if (falling && pending) {
func(...args)
pending = false
}
ready = true
timer = undefined
}, interval)
}
throttled.isThrottled = () => {
return timer !== undefined
Expand Down
33 changes: 29 additions & 4 deletions tests/curry/throttle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,48 @@ describe('throttle', () => {
assert.deepEqual(results, [false, true, true, true, false])
})

test('single call with falling option is set to `true` calls source function once', async () => {
test('single call with trailing option is set to `true` calls source function once', async () => {
let calls = 0
const func = _.throttle({ interval, falling: true }, () => calls++)
const func = _.throttle({ interval, trailing: true }, () => calls++)
func()
expect(calls).toBe(1)
vi.advanceTimersByTime(interval + 10)
expect(calls).toBe(1)
})

test('repeated calls with falling option is set to `true` calls source function again on falling edge', async () => {
test('repeated calls with trailing option is set to `true` calls source function again on trailing edge', async () => {
let calls = 0
const func = _.throttle({ interval, falling: true }, () => calls++)
const func = _.throttle({ interval, trailing: true }, () => calls++)
func()
expect(calls).toBe(1)
vi.advanceTimersByTime(10)
func()
vi.advanceTimersByTime(interval + 10)
expect(calls).toBe(2)
})

test('with trailing option is set to `true`, throttling is still effective after a trailing invocation', async () => {
let calls = 0
const func = _.throttle({ interval, trailing: true }, () => calls++)
func()
func()
expect(calls).toBe(1)
vi.advanceTimersByTime(10)
func()
func()
expect(calls).toBe(1)
vi.advanceTimersByTime(interval)
// By now, the trailing call should have occurred
expect(calls).toBe(2)

vi.advanceTimersByTime(10)
func()
func()
// This call should still be throttled
expect(calls).toBe(2)

vi.advanceTimersByTime(interval + 10)
// By now another trailing call should have happened
expect(calls).toBe(3)
})
})

0 comments on commit 745942a

Please sign in to comment.