Skip to content

Commit c50e70d

Browse files
committed
refactor: throttle reworked with tests
1 parent 8b3be0f commit c50e70d

File tree

5 files changed

+129
-41
lines changed

5 files changed

+129
-41
lines changed

packages/vuetify/src/composables/nested/nested.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,11 @@ export const useNested = (props: NestedProps) => {
221221
const nodeIds = new Set<unknown>()
222222

223223
const itemsUpdatePropagation = throttle(() => {
224-
children.value = new Map(children.value)
225-
parents.value = new Map(parents.value)
224+
// setTimeout makes the checkboxes selection appear faster
225+
setTimeout(() => {
226+
children.value = new Map(children.value)
227+
parents.value = new Map(parents.value)
228+
})
226229
}, 100)
227230

228231
const nested: NestedProvide = {
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { throttle } from '../throttle'
2+
3+
// Utilities
4+
import { wait } from '@test'
5+
6+
describe('throttle', () => {
7+
it('should execute only the calls right before the interval', async () => {
8+
const result = [] as number[]
9+
const pushThrottled = throttle((v: number) => result.push(v), 100, { leading: false, trailing: false })
10+
11+
let lastId = 0
12+
const interval = setInterval(() => pushThrottled(++lastId), 30)
13+
await wait(280)
14+
clearInterval(interval)
15+
16+
expect(result).toStrictEqual([5, 9])
17+
})
18+
19+
it('should execute only the calls right before the interval + trailing one', async () => {
20+
const result = [] as number[]
21+
const pushThrottled = throttle((v: number) => result.push(v), 100, { leading: false, trailing: true })
22+
23+
let lastId = 0
24+
const interval = setInterval(() => pushThrottled(++lastId), 30)
25+
await wait(280)
26+
clearInterval(interval)
27+
await wait(100)
28+
29+
expect(result).toStrictEqual([4, 7, 9])
30+
})
31+
32+
it('should keep throttling after executing trailing call', async () => {
33+
const result = [] as number[]
34+
const pushThrottled = throttle((v: number) => result.push(v), 100, { leading: false, trailing: true })
35+
36+
let lastId = 0
37+
setTimeout(() => pushThrottled(++lastId), 0)
38+
setTimeout(() => pushThrottled(++lastId), 40)
39+
setTimeout(() => pushThrottled(++lastId), 180)
40+
setTimeout(() => pushThrottled(++lastId), 190)
41+
await wait(280)
42+
43+
expect(result).toStrictEqual([2, 4])
44+
})
45+
46+
it('should execute only the calls right before the interval + leading and trailing', async () => {
47+
const result = [] as number[]
48+
const pushThrottled = throttle((v: number) => result.push(v), 100)
49+
50+
let lastId = 0
51+
const interval = setInterval(() => pushThrottled(++lastId), 30)
52+
await wait(280)
53+
clearInterval(interval)
54+
await wait(100)
55+
56+
expect(result).toStrictEqual([1, 4, 7, 9])
57+
})
58+
59+
it('should pass calls the same way when resumed', async () => {
60+
const result = [] as number[]
61+
const pushThrottled = throttle((v: number) => result.push(v), 100)
62+
63+
let lastId = 0
64+
let interval = setInterval(() => pushThrottled(++lastId), 30)
65+
await wait(280)
66+
clearInterval(interval)
67+
await wait(150)
68+
69+
lastId = 200
70+
interval = setInterval(() => pushThrottled(++lastId), 30)
71+
await wait(280)
72+
clearInterval(interval)
73+
await wait(100)
74+
75+
expect(result).toStrictEqual([1, 4, 7, 9, 201, 204, 207, 209])
76+
})
77+
})

packages/vuetify/src/util/helpers.ts

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -420,45 +420,6 @@ export function debounce (fn: Function, delay: MaybeRef<number>) {
420420
return wrap
421421
}
422422

423-
export function throttle<T extends (...args: any[]) => any> (
424-
fn: T,
425-
delay: number,
426-
options = { leading: true, trailing: true },
427-
) {
428-
let timeoutId = 0
429-
let lastExec = 0
430-
let throttling = false
431-
432-
const wrap = (...args: Parameters<T>): void | ReturnType<T> => {
433-
clearTimeout(timeoutId)
434-
const now = Date.now()
435-
const elapsed = now - lastExec
436-
437-
if (!throttling || elapsed >= delay) {
438-
lastExec = now
439-
}
440-
if ((!throttling && options.leading) || elapsed >= delay) {
441-
window.setTimeout(() => fn(...args)) // ignore 'fn' executin errors
442-
}
443-
444-
throttling = true
445-
timeoutId = window.setTimeout(() => {
446-
throttling = false
447-
if (options.trailing) {
448-
fn(...args)
449-
}
450-
}, delay)
451-
}
452-
453-
wrap.clear = () => {
454-
clearTimeout(timeoutId)
455-
lastExec = 0
456-
throttling = false
457-
}
458-
wrap.immediate = fn
459-
return wrap
460-
}
461-
462423
export function clamp (value: number, min = 0, max = 1) {
463424
return Math.max(min, Math.min(max, value))
464425
}

packages/vuetify/src/util/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ export * from './isFixedPosition'
1818
export * from './propsFactory'
1919
export * from './useRender'
2020
export * from './timeUtils'
21+
export * from './throttle'
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
export function throttle<T extends (...args: any[]) => any> (
2+
fn: T,
3+
delay: number,
4+
options = { leading: true, trailing: true },
5+
) {
6+
let timeoutId = 0
7+
let lastExec = 0
8+
let throttling = false
9+
let start = 0
10+
11+
function clear () {
12+
clearTimeout(timeoutId)
13+
throttling = false
14+
start = 0
15+
}
16+
17+
const wrap = (...args: Parameters<T>): void | ReturnType<T> => {
18+
clearTimeout(timeoutId)
19+
20+
const now = Date.now()
21+
22+
if (!start) start = now
23+
const elapsed = now - Math.max(start, lastExec)
24+
25+
function invoke () {
26+
lastExec = Date.now()
27+
timeoutId = window.setTimeout(clear, delay)
28+
fn(...args)
29+
}
30+
31+
if (!throttling) {
32+
throttling = true
33+
if (options.leading) {
34+
invoke()
35+
}
36+
} else if (elapsed >= delay) {
37+
invoke()
38+
} else if (options.trailing) {
39+
timeoutId = window.setTimeout(invoke, delay - elapsed)
40+
}
41+
}
42+
43+
wrap.clear = clear
44+
wrap.immediate = fn
45+
return wrap
46+
}

0 commit comments

Comments
 (0)