Skip to content

Commit

Permalink
feat(throttleFilter): support object as argument (#3722)
Browse files Browse the repository at this point in the history
Co-authored-by: Anthony Fu <github@antfu.me>
  • Loading branch information
17359898647 and antfu committed Feb 20, 2024
1 parent 668ca14 commit 66d09b5
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 4 deletions.
34 changes: 31 additions & 3 deletions packages/shared/utils/filters.ts
@@ -1,4 +1,4 @@
import { readonly, ref } from 'vue-demi'
import { isRef, readonly, ref } from 'vue-demi'
import { toValue } from '../toValue'
import { noop } from './is'
import type { AnyFn, ArgumentsType, Awaited, MaybeRefOrGetter, Pausable, Promisify } from './types'
Expand Down Expand Up @@ -114,6 +114,25 @@ export function debounceFilter(ms: MaybeRefOrGetter<number>, options: DebounceFi
return filter
}

export interface ThrottleFilterOptions {
/**
* The maximum time allowed to be delayed before it's invoked.
*/
delay: MaybeRefOrGetter<number>
/**
* Whether to invoke on the trailing edge of the timeout.
*/
trailing?: boolean
/**
* Whether to invoke on the leading edge of the timeout.
*/
leading?: boolean
/**
* Whether to reject the last call if it's been cancel.
*/
rejectOnCancel?: boolean
}

// TODO v11: refactor the params to object
/**
* Create an EventFilter that throttle the events
Expand All @@ -123,13 +142,22 @@ export function debounceFilter(ms: MaybeRefOrGetter<number>, options: DebounceFi
* @param [leading]
* @param [rejectOnCancel]
*/
export function throttleFilter(ms: MaybeRefOrGetter<number>, trailing = true, leading = true, rejectOnCancel = false) {
export function throttleFilter(ms: MaybeRefOrGetter<number>, trailing?: boolean, leading?: boolean, rejectOnCancel?: boolean): EventFilter
export function throttleFilter(options: ThrottleFilterOptions): EventFilter
export function throttleFilter(...args: any[]) {
let lastExec = 0
let timer: ReturnType<typeof setTimeout> | undefined
let isLeading = true
let lastRejector: AnyFn = noop
let lastValue: any

let ms: MaybeRefOrGetter<number>
let trailing: boolean
let leading: boolean
let rejectOnCancel: boolean
if (!isRef(args[0]) && typeof args[0] === 'object')
({ delay: ms, trailing = true, leading = true, rejectOnCancel = false } = args[0])
else
[ms, trailing = true, leading = true, rejectOnCancel = false] = args
const clear = () => {
if (timer) {
clearTimeout(timer)
Expand Down
142 changes: 141 additions & 1 deletion packages/shared/utils/index.test.ts
Expand Up @@ -137,7 +137,6 @@ describe('filters', () => {
it('should throttle', () => {
const throttledFilterSpy = vi.fn()
const filter = createFilterWrapper(throttleFilter(1000), throttledFilterSpy)

setTimeout(filter, 500)
setTimeout(filter, 500)
setTimeout(filter, 500)
Expand Down Expand Up @@ -353,3 +352,144 @@ describe('compatibility', () => {
}
})
})

describe('optionsFilters', () => {
beforeEach(() => {
vi.useFakeTimers()
})
it('optionsThrottleFilter should throttle', () => {
const throttledFilterSpy = vi.fn()
const filter = createFilterWrapper(throttleFilter({
delay: 1000,
}), throttledFilterSpy)
setTimeout(filter, 500)
setTimeout(filter, 500)
setTimeout(filter, 500)
setTimeout(filter, 500)

vi.runAllTimers()

expect(throttledFilterSpy).toHaveBeenCalledTimes(2)
})

it('optionsThrottleFilter should throttle evenly', () => {
const debouncedFilterSpy = vi.fn()

const filter = createFilterWrapper(throttleFilter({
delay: 1000,
}), debouncedFilterSpy)

setTimeout(() => filter(1), 500)
setTimeout(() => filter(2), 1000)
setTimeout(() => filter(3), 2000)

vi.runAllTimers()

expect(debouncedFilterSpy).toHaveBeenCalledTimes(3)
expect(debouncedFilterSpy).toHaveBeenCalledWith(1)
expect(debouncedFilterSpy).toHaveBeenCalledWith(2)
expect(debouncedFilterSpy).toHaveBeenCalledWith(3)
})

it('optionsThrottleFilter should throttle with ref', () => {
const debouncedFilterSpy = vi.fn()
const throttle = ref(0)
const filter = createFilterWrapper(throttleFilter(throttle), debouncedFilterSpy)

filter()
throttle.value = 1000

setTimeout(filter, 300)
setTimeout(filter, 600)
setTimeout(filter, 900)

vi.runAllTimers()

expect(debouncedFilterSpy).toHaveBeenCalledTimes(2)
})

it('optionsThrottleFilter should not duplicate single event', () => {
const debouncedFilterSpy = vi.fn()
const filter = createFilterWrapper(throttleFilter({
delay: 1000,
}), debouncedFilterSpy)

setTimeout(filter, 500)

vi.runAllTimers()

expect(debouncedFilterSpy).toHaveBeenCalledTimes(1)
})

it('optionsThrottleFilter should get trailing value', () => {
const sumSpy = vi.fn((a: number, b: number) => a + b)
const throttledSum = createFilterWrapper(
throttleFilter({
delay: 1000,
trailing: true,
}),
sumSpy,
)

let result = throttledSum(2, 3)
setTimeout(() => {
result = throttledSum(4, 5)
}, 600)
setTimeout(() => {
result = throttledSum(6, 7)
}, 900)

vi.runAllTimers()

expect(sumSpy).toHaveBeenCalledTimes(2)
expect(result).resolves.toBe(6 + 7)

setTimeout(() => {
result = throttledSum(8, 9)
}, 1200)
setTimeout(() => {
result = throttledSum(10, 11)
}, 1800)

vi.runAllTimers()

expect(sumSpy).toHaveBeenCalledTimes(4)
expect(result).resolves.toBe(10 + 11)
})

it('optionsThrottleFilter should get leading value', () => {
const sumSpy = vi.fn((a: number, b: number) => a + b)
const throttledSum = createFilterWrapper(
throttleFilter({
delay: 1000,
trailing: false,
}),
sumSpy,
)

let result = throttledSum(2, 3)
setTimeout(() => {
result = throttledSum(4, 5)
}, 600)
setTimeout(() => {
result = throttledSum(6, 7)
}, 900)

vi.runAllTimers()

expect(sumSpy).toHaveBeenCalledTimes(1)
expect(result).resolves.toBe(2 + 3)

setTimeout(() => {
result = throttledSum(8, 9)
}, 1200)
setTimeout(() => {
result = throttledSum(10, 11)
}, 1800)

vi.runAllTimers()

expect(sumSpy).toHaveBeenCalledTimes(2)
expect(result).resolves.toBe(8 + 9)
})
})

0 comments on commit 66d09b5

Please sign in to comment.