-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
ref(replay): Replace lodash.debounce
with custom debounce implementation
#6593
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
10e0288
df01c9f
584f294
b7ae986
8b0bc87
853b98a
816309b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
type DebouncedCallback = { | ||
flush: () => void | unknown; | ||
cancel: () => void; | ||
(): void | unknown; | ||
}; | ||
type CallbackFunction = () => unknown; | ||
type DebounceOptions = { maxWait?: number }; | ||
|
||
/** | ||
* Heavily simplified debounce function based on lodash.debounce. | ||
* | ||
* This function takes a callback function (@param fun) and delays its invocation | ||
* by @param wait milliseconds. Optionally, a maxWait can be specified in @param options, | ||
* which ensures that the callback is invoked at least once after the specified max. wait time. | ||
* | ||
* @param func the function whose invocation is to be debounced | ||
* @param wait the minimum time until the function is invoked after it was called once | ||
* @param options the options object, which can contain the `maxWait` property | ||
* | ||
* @returns the debounced version of the function, which needs to be called at least once to start the | ||
* debouncing process. Subsequent calls will reset the debouncing timer and, in case @paramfunc | ||
* was already invoked in the meantime, return @param func's return value. | ||
* The debounced function has two additional properties: | ||
* - `flush`: Invokes the debounced function immediately and returns its return value | ||
* - `cancel`: Cancels the debouncing process and resets the debouncing timer | ||
*/ | ||
export function debounce(func: CallbackFunction, wait: number, options?: DebounceOptions): DebouncedCallback { | ||
let callbackReturnValue: unknown; | ||
|
||
let timerId: ReturnType<typeof setTimeout> | undefined; | ||
let maxTimerId: ReturnType<typeof setTimeout> | undefined; | ||
|
||
const maxWait = options && options.maxWait ? Math.max(options.maxWait, wait) : 0; | ||
|
||
function invokeFunc(): unknown { | ||
cancelTimers(); | ||
callbackReturnValue = func(); | ||
return callbackReturnValue; | ||
} | ||
|
||
function cancelTimers(): void { | ||
timerId !== undefined && clearTimeout(timerId); | ||
maxTimerId !== undefined && clearTimeout(maxTimerId); | ||
timerId = maxTimerId = undefined; | ||
} | ||
|
||
function flush(): unknown { | ||
if (timerId !== undefined || maxTimerId !== undefined) { | ||
return invokeFunc(); | ||
} | ||
return callbackReturnValue; | ||
} | ||
|
||
function debounced(): unknown { | ||
if (timerId) { | ||
clearTimeout(timerId); | ||
} | ||
timerId = setTimeout(invokeFunc, wait); | ||
|
||
if (maxWait && maxTimerId === undefined) { | ||
maxTimerId = setTimeout(invokeFunc, maxWait); | ||
} | ||
|
||
return callbackReturnValue; | ||
} | ||
|
||
debounced.cancel = cancelTimers; | ||
debounced.flush = flush; | ||
return debounced; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -54,7 +54,7 @@ describe('Replay (errorSampleRate)', () => { | |
expect(replay).not.toHaveLastSentReplay(); | ||
|
||
captureException(new Error('testing')); | ||
jest.runAllTimers(); | ||
jest.advanceTimersByTime(5000); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had to replace a couple of WDYT, does this change make sense? @billyvg you know these tests best. Is there something I'm missing? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure, I would think Should we replace There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My theory is that the cancellation of the two timers when one of them is invoked might somehow interfere with whatever
Sure can do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
On second thought, let's do this in a separate PR, just to not clutter this one. I've checked and we're using 5000 quite a lot in tests. Edit: #6612 |
||
await new Promise(process.nextTick); | ||
|
||
expect(replay).toHaveSentReplay({ | ||
|
@@ -99,8 +99,7 @@ describe('Replay (errorSampleRate)', () => { | |
events: JSON.stringify([{ data: { isCheckout: true }, timestamp: BASE_TIMESTAMP + 5020, type: 2 }]), | ||
}); | ||
|
||
jest.runAllTimers(); | ||
await new Promise(process.nextTick); | ||
jest.advanceTimersByTime(5000); | ||
|
||
// New checkout when we call `startRecording` again after uploading segment | ||
// after an error occurs | ||
|
@@ -118,8 +117,10 @@ describe('Replay (errorSampleRate)', () => { | |
domHandler({ | ||
name: 'click', | ||
}); | ||
jest.runAllTimers(); | ||
|
||
jest.advanceTimersByTime(5000); | ||
await new Promise(process.nextTick); | ||
|
||
expect(replay).toHaveLastSentReplay({ | ||
events: JSON.stringify([ | ||
{ | ||
|
@@ -297,7 +298,7 @@ describe('Replay (errorSampleRate)', () => { | |
|
||
captureException(new Error('testing')); | ||
|
||
jest.runAllTimers(); | ||
jest.advanceTimersByTime(5000); | ||
await new Promise(process.nextTick); | ||
|
||
expect(replay).toHaveSentReplay({ | ||
|
@@ -398,7 +399,8 @@ it('sends a replay after loading the session multiple times', async () => { | |
expect(replay).not.toHaveLastSentReplay(); | ||
|
||
captureException(new Error('testing')); | ||
jest.runAllTimers(); | ||
|
||
jest.advanceTimersByTime(5000); | ||
await new Promise(process.nextTick); | ||
|
||
expect(replay).toHaveSentReplay({ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since build changes are always risky, I double checked NPM packages and CDN bundles and they still work with my test apps.