7 Commits

Author SHA1 Message Date
dependabot[bot] ce8f298016 Bump braces from 3.0.2 to 3.0.3
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-13 10:01:20 +00:00
dependabot[bot] ff58dd36f8 Bump word-wrap from 1.2.3 to 1.2.4
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.4.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-21 13:03:26 +02:00
Michael Fabian 'Xaymar' Dirks 9ae45f51ce Publish v0.3.0
- RateLimiter is no longer the default export, breaks normal JavaScript.
- Code is now somewhat properly tested, to ensure that at least basic functionality is there.
2023-03-19 02:14:40 +01:00
Michael Fabian 'Xaymar' Dirks 90426607a4 Add a way to print messages, and add a proper test for rate limiting 2023-03-19 02:13:44 +01:00
Michael Fabian 'Xaymar' Dirks 28c96ba9b9 Add some basic tests
Not much else can be tested here. This checks that things are being run, and that their return values are being returned. Not sure how we would verify that the rate limit is being enforced yet.
2023-03-19 01:51:44 +01:00
Michael Fabian 'Xaymar' Dirks 4db9ab23eb Revert default export
TypeScript does not automatically assign the only default export to exports, or at least not with my configuration. Not worth testing around, and this also makes it easier to use for normal JavaScript.
2023-03-19 01:50:58 +01:00
Michael Fabian 'Xaymar' Dirks 3483a6c60e Add important comments to document functionality 2023-03-19 00:41:06 +01:00
5 changed files with 1818 additions and 1682 deletions
+2 -2
View File
@@ -8,7 +8,7 @@ Simple but effective way to rate limit Tasks in JavaScript. Anything can be rate
## Usage
```js
var RateLimiter = require("@xaymar/ratelimiter");
var { RateLimiter } = require("@xaymar/ratelimiter");
let limitMany = new RateLimiter(4);
let limitOne = new RateLimiter(1);
@@ -40,7 +40,7 @@ No, but it is relatively easy to do without official support. See the example be
```js
// main.js
var RateLimiter = require("@xaymar/ratelimiter");
var { RateLimiter } = require("@xaymar/ratelimiter");
let worker = new Worker("worker.js");
let workerRL = new RateLimiter(1);
+17 -13
View File
@@ -1,13 +1,13 @@
{
"name": "ratelimiter",
"version": "1.0.0",
"version": "0.3.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ratelimiter",
"version": "1.0.0",
"license": "BSD",
"version": "0.3.0",
"license": "GPLv3",
"devDependencies": {
"@types/express": "^4.17.17",
"@types/node": "^18.15.3",
@@ -15,6 +15,10 @@
"@typescript-eslint/parser": "^5.43.0",
"eslint": "^8.27.0",
"typescript": "^4.9.3"
},
"funding": {
"type": "patreon",
"url": "https://www.patreon.com/xaymar"
}
},
"node_modules/@eslint-community/eslint-utils": {
@@ -510,12 +514,12 @@
}
},
"node_modules/braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"dependencies": {
"fill-range": "^7.0.1"
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
@@ -889,9 +893,9 @@
}
},
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"dependencies": {
"to-regex-range": "^5.0.1"
@@ -1643,9 +1647,9 @@
}
},
"node_modules/word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
"integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
+2 -2
View File
@@ -4,7 +4,7 @@
"description": "A simple but effective way to rate limit Tasks in JavaScript.",
"license": "GPLv3",
"repository": "https://github.com/Xaymar/js-ratelimiter",
"version": "0.2.0",
"version": "0.3.0",
"funding": {
"type": "patreon",
"url": "https://www.patreon.com/xaymar"
@@ -16,7 +16,7 @@
"compile": "npx tsc",
"build": "npm run fix && npm run compile",
"prepublish": "npm run build",
"test": "echo \"Error: no test specified\" && exit 1"
"test": "node ./tests/test.js"
},
"devDependencies": {
"@types/express": "^4.17.17",
+14 -1
View File
@@ -28,11 +28,18 @@ type RateLimiterAsyncExecutor = (...args: any[]) => Promise<any>;
type RateLimiterSyncExecutor = (...args: any[]) => any;
type RateLimiterExecutor = RateLimiterSyncExecutor | RateLimiterAsyncExecutor;
export default class RateLimiter {
/** A simple but effective way to rate limit Tasks.
*
*/
export class RateLimiter {
private _maximum: number = 0;
private _available: number = 0;
private _instances: any[];
/** Create a new instance of a RateLimiter.
*
* @param limit The maximum number of tasks that should run in parallel. The Default is 2/3rds of the available CPU threads.
*/
constructor(limit?: number) {
if (!limit) {
this._maximum = Math.ceil(Math.max(1, os.cpus().length / 3 * 2));
@@ -43,6 +50,12 @@ export default class RateLimiter {
this._instances = [];
}
/** Queue up a new task.
*
* @param executor The function that should be run eventually.
* @param args Optional arguments to pass to the function.
* @returns A Promise that resolves with the result of the function.
*/
async queue(executor: RateLimiterExecutor, ...args: any[]) {
// Use async/await to find a free slot.
while (this._available == 0) {
+119
View File
@@ -0,0 +1,119 @@
// May require `npm link`.
const { RateLimiter } = require("../generated/ratelimiter");
async function asyncRunTest(fn, ...args) {
try {
let msg = await fn(...args);
if (msg) {
console.log(`${fn.name}(${args}): ${msg}`);
} else {
console.log(`${fn.name}(${args})`);
}
} catch (ex) {
console.log(`${fn.name}(${args})`)
console.error(ex);
process.exitCode++;
}
}
function runTest(fn, ...args) {
try {
let msg = fn(...args);
if (msg) {
console.log(`${fn.name}(${args}): ${msg}`);
} else {
console.log(`${fn.name}(${args})`);
}
} catch (ex) {
console.log(`${fn.name}(${args})`);
console.error(ex);
process.exitCode++;
}
}
async function delay(time) {
await new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, time);
});
}
function test_Construct(limit) {
let rl = new RateLimiter(limit);
}
async function test_TaskLimit(tasks, limit) {
let rl = new RateLimiter(limit);
let p = new Array();
for (let idx = 0; idx < tasks; idx++) {
p.push(rl.queue(() => {
return true;
}));
}
let r = await Promise.all(p);
for (res of r) {
if (res !== true) {
throw new Error("Result is unexpected.");
}
}
}
async function test_LimitEnforcement(tasks, limit) {
let rl = new RateLimiter(limit);
let value = 0;
let time = 50;
let records = [];
let t = setInterval(() => {
records.push([
value, Date.now()
]);
}, 25);
let p = new Array();
for (let idx = 0; idx < tasks; idx++) {
p.push(rl.queue(async () => {
await delay(time);
value++;
}));
}
await Promise.all(p);
clearInterval(t);
let totalTime = 0;
let totalValue = 0;
for (let n = 1; n < records.length; n++) {
let pRecord = records[n - 1];
let cRecord = records[n];
let deltaTime = cRecord[1] - pRecord[1];
let deltaValue = cRecord[0] - pRecord[0];
totalTime += deltaTime;
totalValue += deltaValue;
}
let averageChange = totalValue / totalTime;
let normalizedChange = averageChange * time;
// setTimeout loses accuracy over time, not sure what the solution is. Accepting +-1 for now.
if ((normalizedChange < (limit - 1.)) || (normalizedChange > (limit + 1.))) {
throw new Error(`Outside acceptable range (${normalizedChange} is not equal to ${limit}).`);
} else {
return `Within acceptable range (${normalizedChange} is roughly equal to ${limit}).`
}
}
(async function() {
process.exitCode = 0;
for (let limit = 1; limit <= 64; limit *= 2) {
runTest(test_Construct, limit);
}
for (let limit = 1; limit <= 10; limit++) {
for (let tasks = 1; tasks <= 64; tasks *= 2) {
await asyncRunTest(test_TaskLimit, tasks, limit);
}
}
for (let limit = 1; limit <= 10; limit++) {
await asyncRunTest(test_LimitEnforcement, 100, limit);
}
})();