Skip to content

Commit 8fdc2f7

Browse files
MarlonPassos-gitmarlon Felipe Passos
and
marlon Felipe Passos
authored
Feature/debounce with cancel and flush (#118)
* test(curry - debounce): handle with cancel * test(curry - debounce): handle with flush method and side effects * feat(curry - debounce): inplements *.cancel and *.flush * test(curry - debounce): show case if cancel past invocations * refactor(curry - debounce): improves typing of timer * docs(curry - debounce): talk about cancel and flush methods Co-authored-by: marlon Felipe Passos <[email protected]>
1 parent 088070c commit 8fdc2f7

File tree

6 files changed

+120
-21
lines changed

6 files changed

+120
-21
lines changed

Diff for: cdn/radash.esm.js

+14-3
Original file line numberDiff line numberDiff line change
@@ -492,11 +492,22 @@ const memo = (func, {
492492
return memoize({}, func, key, ttl);
493493
};
494494
const debounce = ({ delay }, func) => {
495-
let timer = null;
495+
let timer = void 0;
496+
let active = true;
496497
const debounced = (...args) => {
497-
clearTimeout(timer);
498-
timer = setTimeout(() => func(...args), delay);
498+
if (active) {
499+
clearTimeout(timer);
500+
timer = setTimeout(() => {
501+
active && func(...args);
502+
}, delay);
503+
} else {
504+
func(...args);
505+
}
506+
};
507+
debounced.cancel = () => {
508+
active = false;
499509
};
510+
debounced.flush = (...args) => func(...args);
500511
return debounced;
501512
};
502513
const throttle = ({ interval }, func) => {

Diff for: cdn/radash.js

+14-3
Original file line numberDiff line numberDiff line change
@@ -495,11 +495,22 @@ var radash = (function (exports) {
495495
return memoize({}, func, key, ttl);
496496
};
497497
const debounce = ({ delay }, func) => {
498-
let timer = null;
498+
let timer = void 0;
499+
let active = true;
499500
const debounced = (...args) => {
500-
clearTimeout(timer);
501-
timer = setTimeout(() => func(...args), delay);
501+
if (active) {
502+
clearTimeout(timer);
503+
timer = setTimeout(() => {
504+
active && func(...args);
505+
}, delay);
506+
} else {
507+
func(...args);
508+
}
509+
};
510+
debounced.cancel = () => {
511+
active = false;
502512
};
513+
debounced.flush = (...args) => func(...args);
503514
return debounced;
504515
};
505516
const throttle = ({ interval }, func) => {

Diff for: cdn/radash.min.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "radash",
3-
"version": "10.0.0",
3+
"version": "10.1.0",
44
"description": "Functional utility library - modern, simple, typed, powerful",
55
"main": "dist/cjs/index.cjs",
66
"module": "dist/esm/index.mjs",
@@ -48,4 +48,4 @@
4848
"engines": {
4949
"node": ">=14.18.0"
5050
}
51-
}
51+
}

Diff for: src/curry.ts

+36-7
Original file line numberDiff line numberDiff line change
@@ -91,21 +91,50 @@ export const memo = <TFunc extends Function>(
9191
return memoize({}, func as any, key, ttl) as any as TFunc
9292
}
9393

94+
export type DebounceFunction<TArgs extends any[]> = {
95+
(...args: TArgs): void
96+
/**
97+
* Cancels the debounced function
98+
*/
99+
cancel(): void
100+
/**
101+
* Runs the debounced function immediately
102+
*/
103+
flush(...args: TArgs): void
104+
}
105+
94106
/**
95107
* Given a delay and a function returns a new function
96108
* that will only call the source function after delay
97-
* milliseconds have passed without any invocations
109+
* milliseconds have passed without any invocations.
110+
*
111+
* The debounce function comes with a `cancel` method
112+
* to cancel delayed `func` invocations and a `flush`
113+
* method to invoke them immediately
98114
*/
99115
export const debounce = <TArgs extends any[]>(
100116
{ delay }: { delay: number },
101117
func: (...args: TArgs) => any
102-
): ((...args: TArgs) => void) => {
103-
let timer: any = null
104-
const debounced = (...args: TArgs) => {
105-
clearTimeout(timer)
106-
timer = setTimeout(() => func(...args), delay)
118+
) => {
119+
let timer: NodeJS.Timeout | undefined = undefined
120+
let active = true
121+
122+
const debounced: DebounceFunction<TArgs> = (...args: TArgs) => {
123+
if (active) {
124+
clearTimeout(timer)
125+
timer = setTimeout(() => {
126+
active && func(...args)
127+
}, delay)
128+
} else {
129+
func(...args)
130+
}
107131
}
108-
return debounced as unknown as (...args: TArgs) => void
132+
debounced.cancel = () => {
133+
active = false
134+
}
135+
debounced.flush = (...args: TArgs) => func(...args)
136+
137+
return debounced
109138
}
110139

111140
/**

Diff for: src/tests/curry.test.ts

+53-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { assert } from 'chai'
22
import * as _ from '..'
3+
import { DebounceFunction } from '../curry'
34

45
describe('curry module', () => {
56
describe('compose function', () => {
@@ -147,15 +148,62 @@ describe('curry module', () => {
147148
})
148149

149150
describe('debounce function', () => {
150-
test('only executes once when called rapidly', async () => {
151-
let calls = 0
152-
const func = _.debounce({ delay: 600 }, () => calls++)
151+
let func: DebounceFunction<any>
152+
const mockFunc = jest.fn()
153+
const runFunc3Times = () => {
153154
func()
154155
func()
155156
func()
156-
assert.equal(calls, 0)
157+
}
158+
159+
beforeEach(() => {
160+
func = _.debounce({ delay: 600 }, mockFunc)
161+
})
162+
163+
afterEach(() => {
164+
jest.clearAllMocks()
165+
})
166+
167+
test('only executes once when called rapidly', async () => {
168+
runFunc3Times()
169+
expect(mockFunc).toHaveBeenCalledTimes(0)
157170
await _.sleep(610)
158-
assert.equal(calls, 1)
171+
expect(mockFunc).toHaveBeenCalledTimes(1)
172+
})
173+
174+
test('does not debounce after cancel is called', () => {
175+
runFunc3Times()
176+
expect(mockFunc).toHaveBeenCalledTimes(0)
177+
func.cancel()
178+
runFunc3Times()
179+
expect(mockFunc).toHaveBeenCalledTimes(3)
180+
runFunc3Times()
181+
expect(mockFunc).toHaveBeenCalledTimes(6)
182+
})
183+
184+
test('when we call the flush method it should execute the function immediately', () => {
185+
func.flush()
186+
expect(mockFunc).toHaveBeenCalledTimes(1)
187+
})
188+
189+
test('continues to debounce after flush is called', async () => {
190+
runFunc3Times()
191+
expect(mockFunc).toHaveBeenCalledTimes(0)
192+
func.flush()
193+
expect(mockFunc).toHaveBeenCalledTimes(1)
194+
func()
195+
expect(mockFunc).toHaveBeenCalledTimes(1)
196+
await _.sleep(610)
197+
expect(mockFunc).toHaveBeenCalledTimes(2)
198+
func.flush()
199+
expect(mockFunc).toHaveBeenCalledTimes(3)
200+
})
201+
202+
test('cancels all pending invocations when cancel is called', async () => {
203+
func()
204+
func.cancel()
205+
await _.sleep(610)
206+
expect(mockFunc).toHaveBeenCalledTimes(0)
159207
})
160208
})
161209

0 commit comments

Comments
 (0)