-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* wip * test++ * add rateLimit to default functions * add example and readme * copyright++, lock files++ * fix import --------- Co-authored-by: Sergey Sergeev <[email protected]>
- Loading branch information
1 parent
6c70764
commit 77f5037
Showing
8 changed files
with
221 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
#try running this in REPL with: .init -f example/debounce.yaml --tail "/acc/- 0" | ||
acc: [] | ||
appendAcc: "${ function($v){$set('/acc/-', $v)} ~> $rateLimit(100)}" #function to append $v to acc array, debounced to 15 ms | ||
counter: "${ function(){($set('/count', $$.count+1); $$.count)} }" #function to increment a count | ||
count: 0 | ||
rapidCaller: "${$setInterval(counter~>appendAcc, 10)}" #increment the count every 10 ms, and send result to appendAcc function which is debounced to 15 ms | ||
stop: "${ count=100?($clearInterval($$.rapidCaller);'done'):'not done' }" #stop when we reached count of 100. Only 100 should wind up in acc array | ||
accCount: "${ $count(acc) }" #count of items in acc array |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
// Copyright 2023 Cisco Systems, Inc. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
import { rateLimit } from "../../../dist/src/utils/rateLimit.js"; | ||
import {expect, jest} from '@jest/globals' | ||
|
||
describe('rateLimit function', () => { | ||
jest.useFakeTimers(); | ||
/** | ||
* Below test validates the following scenario | ||
* rateLimitedFunction('First call'); // called at 0ms and Executed immediately | ||
* rateLimitedFunction('Second call'); // called at 500ms, deferred till execution at 1000ms | ||
* rateLimitedFunction('Third call'); // called at 700ms, deferred till execution at 1000ms, and replaces the Second call | ||
* // at 1000ms 'Third call' gets executed. | ||
* rateLimitedFunction('Forth call'); // called at 1100ms and gets executed in 2000ms | ||
**/ | ||
it('should rate limit function calls as specified', () => { | ||
const mockFunction = jest.fn(); | ||
const maxWait = 1000; | ||
const rateLimitedFunction = rateLimit(mockFunction, maxWait); | ||
|
||
// First call - executed immediately | ||
rateLimitedFunction('First call'); | ||
expect(mockFunction).toHaveBeenCalledTimes(1); | ||
expect(mockFunction).toHaveBeenCalledWith('First call'); | ||
|
||
// Second call - deferred | ||
jest.advanceTimersByTime(500); | ||
rateLimitedFunction('Second call'); | ||
expect(mockFunction).toHaveBeenCalledTimes(1); | ||
|
||
// Third call - replaces second, also deferred | ||
jest.advanceTimersByTime(200); | ||
rateLimitedFunction('Third call'); | ||
expect(mockFunction).toHaveBeenCalledTimes(1); | ||
|
||
// Executing the deferred 'Third call' | ||
jest.advanceTimersByTime(350); | ||
expect(mockFunction).toHaveBeenCalledTimes(2); | ||
expect(mockFunction).toHaveBeenCalledWith('Third call'); | ||
|
||
// Fourth call - at 1100ms from start gets defferred till 2000ms | ||
jest.advanceTimersByTime(100); | ||
rateLimitedFunction('Forth call'); | ||
jest.advanceTimersByTime(900); | ||
expect(mockFunction).toHaveBeenCalledTimes(3); | ||
expect(mockFunction).toHaveBeenCalledWith('Forth call'); | ||
|
||
// no more calls expected | ||
jest.advanceTimersByTime(1000); | ||
expect(mockFunction).toHaveBeenCalledTimes(3); | ||
}); | ||
}); | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// Copyright 2024 Cisco Systems, Inc. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
export function rateLimit<T extends AnyFunction>(func: T, maxWait: number = 1000): T { | ||
let lastCallTime: number | null = null; | ||
let deferredCallTimer: ReturnType<typeof setTimeout> | null = null; | ||
let lastArgs: Parameters<T> | null = null; | ||
|
||
return function (this: ThisParameterType<T>, ...args: Parameters<T>): void { | ||
const context = this as ThisParameterType<T>; | ||
lastArgs = args; // Store the latest arguments | ||
|
||
const executeFunction = () => { | ||
lastCallTime = Date.now(); | ||
if (lastArgs) { | ||
func.apply(context, lastArgs); | ||
lastArgs = null; // Reset after execution | ||
} | ||
}; | ||
|
||
if (lastCallTime === null || (Date.now() - lastCallTime) >= maxWait) { | ||
// If this is the first call, or the wait time has passed since the last call | ||
executeFunction(); | ||
} else if (!deferredCallTimer) { | ||
// Set up a deferred execution if not already scheduled | ||
deferredCallTimer = setTimeout(() => { | ||
deferredCallTimer = null; // Clear the timer | ||
executeFunction(); | ||
}, maxWait - (Date.now() - lastCallTime)); | ||
} | ||
} as T; | ||
} | ||
|
||
type AnyFunction = (...args: any[]) => void; |
Oops, something went wrong.