jhurliman / node-rate-limiter Goto Github PK
View Code? Open in Web Editor NEWA generic rate limiter for node.js. Useful for API clients, web crawling, or other tasks that need to be throttled
License: MIT License
A generic rate limiter for node.js. Useful for API clients, web crawling, or other tasks that need to be throttled
License: MIT License
Hi,
Don't know where to comment this. But how would node-rate-limited be implemented along with socket.io , how Would it be used to Throttle a client.id at sending events?
Hope I get an idea , would like to use your Module..
regards
To prevent brute-force in a multi-server scenario the cache/tokens should be store in some external storage, do you support this?
Hi,
I got this message
Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: …/limiter/dist/cjs/index.js
require() of ES modules is not supported.
require() of …/limiter/dist/cjs/index.js from MyService.js is an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which defines all .js files in that package scope as ES modules.
When trying to require
const RateLimiter = require('limiter').RateLimiter;
Node: v15.14.0
Limiter: 2.0.1
Note that I previously used v1.1.3 and this does not happen.
Limits = 40/day
Once 40 requests are served irrespective of the IP Addresses, It redirects to the 429 view.
So even the visitor whose visiting the site for the first time, is redirected to 429.
Can we have rate limiting for every IP address?
var RateLimiter = require('limiter').RateLimiter;
var limiter = new RateLimiter(40, 'day', true); // fire CB immediately
app.use(function (req, res, next) {
if ((req.headers["user-agent"].indexOf("Googlebot") != -1) || req.url == "/429")
next();
else
limiter.removeTokens(1, function(err, remainingRequests) {
if (remainingRequests < 0) {
res.redirect('/429');
} else {
next();
}
});
});
It would be great if callback could be ignored if it's undefined. Right now it throws an error.
For example, I want to removeTokens but I don't care about the callback. Perhaps you could add a removeTokensSync method? and in removeTokens just check if callback is not null and is a function.
After migrating to webpack 5 the call of the removeTokens
methods crashes the app with the error process is not defined
import { RateLimiter } from 'limiter';
const maxRequestsPerSecond = 10;
const myLimiter = new RateLimiter(maxRequestsPerSecond, 'second');
const someMethod = async () => {
await new Promise<void>(resolve => {
myLimiter.removeTokens(1, () => resolve());
}
}
The npm package is outdated, the index.d.ts
is missing
If you use an invalid period string, eg. because of a typo (new RateLimiter(10, "horu")
), the library will fallback to "second"
, rather than throwing an error. This fallback is completely unexpected, and can cause outright wrong behaviour in the event of typos (as just happened to me).
Documentation does not say anything. The 1st parameter of the removeTokens
is the number of requests to be removed?
limiter.removeTokens(1, function(err, remainingRequests)
Please add a CHANGELOG
Hi!
I am running into a bug when using parentBucket, which is fixed here: 2b99def
Could you make a new version available on NPM so I can benefit from this fix?
Thanks!
I'm assuming this is by IP? Is there anyway I can insert via the options like a parameter to use instead?
For example right now it is set up to not allow more then 5 requests per IP address per minute. But right now I'm hoping to make it by email.
Cannot find out how / where it performs the ban on the user if they exceeded the requests? Is it a IP ban or just returning false in javascript?
I am calling an API that makes the specific requirements.
Obviously, you can satisfy the #1 by using new RateLimiter({"tokensPerInterval":60 ,"interval":"minute"}); You could then create a TokenBuffer for #2 with a parent as #1. However, the TokenBuffer does not add tokens up front and also 'drips' tokens in as time goes by.
Hence, I looked at another solution of using 2 tiered TokenBuckets with behavior similar to RateLimiter.
In order to make a TokenBucket behave initially like the RateLimiter, during construction, it is necessary to see the content at construction time.
This is currently done in RateLimiter as follows:
this.tokenBucket.content = tokensPerInterval;
However, as this is a TokenBucket and not a RateLimiter, tokens are only added during calls to drip(). Ideally, to make TokenBucket behave in the same fashion as RateLimiter, a change to drip() was required.
Here's the code excerpt I modified such that you pass a value for milliseconds (necessary to allow extension of drip() for custom behavior by subclasses) and then use milliseconds accordingly.
this.drip(clock_1.getMilliseconds());
/**
* Add any new tokens to the bucket since the last drip.
* @returns {Boolean} True if new tokens were added, otherwise false.
*/
drip(milliseconds) {
if (this.tokensPerInterval === 0) {
const prevContent = this.content;
this.content = this.bucketSize;
return this.content > prevContent;
}
const now = milliseconds;
const deltaMS = Math.max(now - this.lastDrip, 0);
this.lastDrip = now;
const dripAmount = deltaMS * (this.tokensPerInterval / this.interval);
const prevContent = this.content;
this.content = Math.min(this.content + dripAmount, this.bucketSize);
return Math.floor(this.content) > Math.floor(prevContent);
}
Next, you need to create the functionality you need with a custom drip() and initial seed of the content. Here's a class that does just that.
class RateLimiterEx extends TokenBucket {
constructor({ bucketSize, tokensPerInterval, interval, parentBucket}) {
super({bucketSize, tokensPerInterval, interval, parentBucket});
// initially fill the bucket.
this.content = this.bucketSize;
}
// Overload drip() such that we add tokens only when the interval expires.
drip(milliseconds) {
if (this.tokensPerInterval === 0) {
const prevContent = this.content;
this.content = this.bucketSize;
return this.content > prevContent;
}
// When timer expires, we simply reset content to the bucketSize.
const now = milliseconds;
const deltaMS = Math.max(now - this.lastDrip, 0);
const prevContent = this.content;
if (deltaMS >= this.interval) {
this.content = this.bucketSize;
this.lastDrip = now;
}
return Math.floor(this.content) > Math.floor(prevContent);
}
}
With this implementation, fireImmediately is not supported. Also, there is a slight difference in the 'sleep' behavior such that TokenBucket will sleep 1000ms while awaiting a refresh (to ensure drip() is called every so often whereas RateLimiter sleeps for a larger amount of time.
Here's some sample code that I put together that illustrates the usage.
const { RateLimiter,TokenBucket } = require("limiter");
async function mainTB4() {
optionsTier1 = {"tokensPerInterval":60, "interval":"minute"};
optionsTier2 = {"tokensPerInterval":80, "interval":1000*80};
const tb = new RateLimiterEx({...optionsTier1});
const tb2 = new RateLimiterEx({...optionsTier2,"parentBucket":tb});
let start = new Date().valueOf();
let delta = start;
for (let i=0;i<330;i++) {
//await sleep(100);
let now = new Date().valueOf();
console.log("i:%d el:%d d:%d cr:%o", i,now - start, now - delta, await tb2.removeTokens(1));
delta = now;
}
}
mainTB4();
Attached please find sample code and changes to TokenBucket.js.
RateLimiterEx.zip
I would welcome comments on this. IMO, this approach supports multiple levels of tiered limiting.
My code is as following:
constructor:()=>{
this.RateLimiterOnSeconds = new RateLimiter(4, 1*1000); // 5 requests per second
this.RateLimiterOnHours = new RateLimiter(2000, 1*1000*60*60); // 2000 request per hour
}
sendReq1: ()=>{
schedule(..)
}
sendReq2: ()=>{
schedule(...)
}
schedule: (fn)=>{
this.RateLimiterOnSeconds.removeTokens(1,(err, remainingRequests)=>{
this.RateLimiterOnHours.removeTokens(1, () => {
console.log('request executed in second...',new Date().getTime()/1000, remainingRequests);
fn();
});
});
}
The limit is exceeded.
Indeed, the console.log in the schedule() method prints:
request executed in second... 1487770295.951 4
request executed in second... 1487770296.043 4
request executed in second... 1487770296.219 4
request executed in second... 1487770296.258 4
request executed in second... 1487770296.427 4
request executed in second... 1487770296.56 4
request executed in second... 1487770296.628 4
request executed in second... 1487770296.803 4
request executed in second... 1487770296.831 4
request executed in second... 1487770296.921 4
request executed in second... 1487770297.126 4
request executed in second... 1487770297.214 4
request executed in second... 1487770297.311 4
request executed in second... 1487770297.421 4
request executed in second... 1487770297.605 4
request executed in second... 1487770297.686 4
request executed in second... 1487770297.803 4
and as you can see, in the second 1487770297
there are 7 requests (instead of 5), and number of tokens to remove is constant.
I have to limit requests by user. Let's say a user has a queue with 10 background tasks lined up. Is there a way to push a single manual request to the top of the queue so it get's executed next before the rest of the background jobs?
I am using limiter to throttle calls to a DynamoDB table. DynamoDB will return the Consumed Capacity, which sometimes is 0. I would like to NOT delay the next call if the Consumed Capacity is 0 but I don't see a straight forward way to do this. I have tried setting then number of tokens to 2, then running tryRemoveTokens(1) if Consumed Capacity is > 0. I also tried the opposite and this.limiter.tokensThisInterval+=1;
if Consumed Capacity is 0. Neither seems to work the way I thought it would. tryRemoveTokens
would run into if (count > this.tokenBucket.tokensPerInterval - this.tokensThisInterval)
and return false.
I am probably misunderstanding how this works. Here is example code snippets.
limiter = new RateLimiter(2, 1000);
limiter.removeTokens(2, (err, remainingRequests) => {
query.returnConsumedCapacity().exec((err, items) => {
if (items.ConsumedCapacity > 0 || items.Count > 0) {
limiter.tryRemoveTokens(1);
}
});
});
limiter = new RateLimiter(1, 1000);
limiter.removeTokens(1, (err, remainingRequests) => {
query.returnConsumedCapacity().exec((err, items) => {
if (items.ConsumedCapacity === 0 || items.Count === 0) {
this.limiter.tokensThisInterval+=1;
}
});
});
Importing from esm in Node.js is broken in v2.1.0:
import { RateLimiter } from 'limiter'
^^^^^^^^^^^
SyntaxError: Named export 'RateLimiter' not found. The requested module 'limiter' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:
import pkg from 'limiter';
const { RateLimiter } = pkg;
at ModuleJob._instantiate (node:internal/modules/esm/module_job:105:21)
at async ModuleJob.run (node:internal/modules/esm/module_job:151:5)
at async Loader.import (node:internal/modules/esm/loader:166:24)
at async Object.loadESM (node:internal/process/esm_loader:68:5)
v2.0.1 was working.
I would like to set the HTTP headers:
something like this:
var RateLimiter = require('limiter').RateLimiter;
var limiter = new RateLimiter(150, 'hour', true);
console.log(limiter.limit()); // 150
console.log(limiter.reset()); // 58 (seconds elapsed)
Can I do something like that?
Hey there
I have a list of 100k emails. I'd like to only send 25 per second. I have setup the code like this:
var async = require('async');
var RateLimiter = require('limiter').RateLimiter;
var limiter = new RateLimiter(25, 'second');
var emails = [];
for (var i = 0; i < 100000; i++) {
emails.push(i);
};
async.eachSeries(emails, function(email, nextEmail){
limiter.removeTokens(1, function(err, remainingRequests) {
asyncEmailFunction(email);
return nextEmail();
});
});
// Just as an example
function asyncEmailFunction(email) {
console.log(email);
};
It works great until about 300 emails, then it just bogs down and doesn't complete them as fast.
Hi. I'm hitting the GitHub API using this setting:
const limiter = new RateLimiter(5000, 'hour')
This is within GitHub's documented limit, but I'm still getting throttled because limiter
is making so many concurrent requests. I think this is happening because limiter
comes on strong at first and uses some kind of backoff mechanism to taper down the requests over the course of the hour. Is that right?
Is there a way to limit the number of concurrent requests?
Here's one way I'm thinking I could approximate it:
const limiter = new RateLimiter(Math.floor(5000/60), 'minute')
// or
const limiter = new RateLimiter(Math.floor(5000/60/60), 'second')
Hi
I'm trying to use this library in order to limit number of http call my app is doing to and external provider.
So my rate limited method MUST provide an promise result (api result).
Business method example:
function getDocs() {
return fetch("https://provider.com/api/docs")
}
I'm asking myself on how to do that with this library.
I will soon suggest a linked PR with some usage examples.
I would like to know:
samples/
directory with some ready-to-use examples,Best regards
Support for redis would allow for deployment in a cluster
Hi,
Great job with the package I just have some questions?
var RateLimiter = Npm.require('limiter').RateLimiter;
var cache = Npm.require('memory-cache');
checkBy = req.headers.app_id;
if (cache.get(checkBy)){
var cachedLimiter = cache.get(checkBy);
if (cachedLimiter.getTokensRemaining()>1){
cachedLimiter.removeTokens(1, function(){});
cache.put(checkBy, cachedLimiter, 60000);
return;
}
throw new Error('Too many requests for the app_id: ' + checkBy + '!');
}
else {
var cachedLimiter = new RateLimiter(10, 'sec');
cache.put(checkBy, cachedLimiter, 60000);
return;
}
var cachedLimiter = new RateLimiter(10, 'sec');
the rate limit is now working as expected I put 30 calls in the same second and about 25 gets passed and 5 gets rejected because of the limit although I don't want to allow more than 10 per second? Could you tell me what am I doing wrong?Thank you very much in advance.
Best regards!
I am running this example:
import { RateLimiter } from "limiter";
// Allow 150 requests per hour (the Twitter search limit). Also understands
// 'second', 'minute', 'day', or a number of milliseconds
const limiter = new RateLimiter({ tokensPerInterval: 150, interval: "hour" });
async function sendRequest() {
// This call will throw if we request more than the maximum number of requests
// that were set in the constructor
// remainingRequests tells us how many additional requests could be sent
// right this moment
const remainingRequests = await limiter.removeTokens(1);
callMyRequestSendingFunction(...);
}
and i am getting this error:
this.fireImmediately = fireImmediately ?? false;
^
SyntaxError: Unexpected token '?'
I've been using this package for years to provide rate limiting for a chat application and it has worked really well for me. I use a limiter for message rate (flood protection) and when the limiter has <= 50% tokens remaining, the server should send a warning message to the user.
Some users have always been able to trigger flood protection w/o getting any warning from the server and I have finally figured out why. Please consider the following code:
const RateLimiter = require('limiter').RateLimiter
const limiter = new RateLimiter(10, 'minute')
const remove = () => {
let date = new Date()
let remaining
if (limiter.tryRemoveTokens(1)) {
remaining = limiter.getTokensRemaining()
console.log(`${date} - token removed, remaining: ${remaining}`)
} else {
console.log(`${date} - NO tokens remaining`)
}
}
remove()
setInterval(() => {
remove()
}, 3500)
Note the interval of 3.5 seconds.
This is the output:
Sat May 04 2019 17:05:03 GMT-0400 (EDT) - token removed, remaining: 9
Sat May 04 2019 17:05:07 GMT-0400 (EDT) - token removed, remaining: 8.584
Sat May 04 2019 17:05:10 GMT-0400 (EDT) - token removed, remaining: 8.168
Sat May 04 2019 17:05:14 GMT-0400 (EDT) - token removed, remaining: 7.751999999999999
Sat May 04 2019 17:05:17 GMT-0400 (EDT) - token removed, remaining: 7.336166666666665
Sat May 04 2019 17:05:21 GMT-0400 (EDT) - token removed, remaining: 6.920166666666665
Sat May 04 2019 17:05:24 GMT-0400 (EDT) - token removed, remaining: 6.504166666666665
Sat May 04 2019 17:05:28 GMT-0400 (EDT) - token removed, remaining: 6.088166666666664
Sat May 04 2019 17:05:31 GMT-0400 (EDT) - token removed, remaining: 5.672166666666664
Sat May 04 2019 17:05:35 GMT-0400 (EDT) - token removed, remaining: 5.255999999999998
Sat May 04 2019 17:05:38 GMT-0400 (EDT) - NO tokens remaining
[SNIP]
Sat May 04 2019 17:06:06 GMT-0400 (EDT) - token removed, remaining: 9
You can see here that getTokensRemaining()
never returns a value below 50% of the bucket size at this interval. My expectation is that remaining tokens would decrement 10, 9, 8, 7
etc. so I'm really confused why this is happening. Even though 10 tokens were removed, getTokensRemaining()
reported 5.255999999999998
when it should have reported 0
(at least in my mind).
Plus I might be confused as to how this works, but I would expect tokens to be slowly added back to the bucket over time. What appears to be happening here is that about a minute after the first removal, the bucket is filled back up with 10 tokens.
I've been digging through tickets and the code but I can't quite figure out what's happening here and what I can do about it. This seems like it might be a bug so I thought I'd report it.
I have the following situation,
How would I configure node-rate-limiter for this setup?
Hi,
I have something like this:
import {RateLimiter} from 'limiter';
const dynamoDBLimiter = new RateLimiter(10, 'second');
console.log('before removeTokens');
for(let contact_i = 0; contact_i < company.contacts.length; contact_i++) {
dynamoDBLimiter.removeTokens(1, async function(err, remainingRequests) {
console.log('adding contact');
newContact = await addContact(contact_i);
console.log('new contact added');
});
}
console.log('end of removeTokens');
But the logs I get are like this:
before removeTokens
end of removeTokens
adding contact
new contact added
Am I doing something wrong?
Thanks!
If I specify 20 / minute, and I have thousands of requests per second (for example). Right now I see 20 events happen right away, then nothing for 1 minute, then another burst of 20 events. It would be nice to specify an option for an even distribution so that one event would fire every 3 seconds rather than bursting every minute.
Thanks!
I'm wondering how folks are gracefully cancelling/aborting calls to RateLimiter.removeTokens()?
Here's a sample use case where I'd want to cancel:
class SubscriptionHandler {
constructor(key, handlerFn) {
this.key = key;
this.handlerFn = handlerFn;
this.rateLimiter = new limiter.RateLimiter(FITBIT_LIMIT_PER_USER_PER_HOUR, 'hour');
this.queue = async.queue((notificationId, callback) => {
this.rateLimiter.removeTokens(1, (error, remainingRequests) => {
this._processNotification(notificationId, callback);
});
});
enqueue(data, completionCallback) {
this.queue.push(data.id);
}
const myHandler = new SubscriptionHandler(myKey, myFBDataFetch);
Step (5) is what's in question. I'd like to cancel the wait on removeTokens() and allow the handler instance to be garbage collected:
class SubscriptionHandler {
constructor(key, handlerFn) {
this.key = key;
this.handlerFn = handlerFn;
this.rateLimiter = new limiter.RateLimiter(FITBIT_LIMIT_PER_USER_PER_HOUR, 'hour');
this.queue = async.queue((notificationId, callback) => {
this.tokenTimeoutID = this.rateLimiter.removeTokens(1, (error, remainingRequests) => {
this._processNotification(notificationId, callback);
});
});
release() {
// Maybe something like this?
clearTimeout(this.tokenTimeoutID);
delete this.rateLimiter;
this.queue.kill();
delete this.queue;
}
}
myHandler.release();
delete myHandler;
Unfortunately, with the existing implementation of RateLimiter and its internal TokenBucket, there is no way to obtain the timeoutID
assigned when TokenBucket invokes its internal comeBackLater()
method. (See https://github.com/jhurliman/node-rate-limiter/blob/master/lib/tokenBucket.js, line 108). Thus, there is always an outstanding reference to the SubscriptionHandler instance, and i cannot get it garbage collected.
If the timer ID allocated by setTimeout() were returned from comeBackLater()
(rather than the false
value currently returned) and bubbled back out as the return value from RateLimiter::removeTokens(), then I could cancel it.
Obviously that breaks encapsulation, and perhaps the RateLimiter or its internal TokenBucket could keep rack of the timer ID and have methods for cancelling it, but I'm simply trying to get my idea across...
Given the above scenario, what would you recommend? @jhurliman ?
(Note that I prefer the semantics of the asynchronous removeTokens() method, and would rather not switch to my own periodic retry calling tryRemoveTokens()...)
Not sure if this is a case of "pilot error" but after upgrading this package and switching to 'import' from 'require' in my code, I'm getting the following error (when I try to run my tests with mocha):
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/Volumes/RAID1/projects/threeceeUtilities/node_modules/limiter/dist/esm/RateLimiter' imported from /Volumes/RAID1/projects/threeceeUtilities/node_modules/limiter/dist/esm/index.js
at finalizeResolution (internal/modules/esm/resolve.js:276:11)
at moduleResolve (internal/modules/esm/resolve.js:699:10)
at Loader.defaultResolve [as _resolve] (internal/modules/esm/resolve.js:810:11)
at Loader.resolve (internal/modules/esm/loader.js:86:40)
at Loader.getModuleJob (internal/modules/esm/loader.js:230:28)
at ModuleWrap. (internal/modules/esm/module_job.js:56:40)
at link (internal/modules/esm/module_job.js:55:36)
I'm importing as in the README:
import { RateLimiter } from "limiter";
I've added the most recent version ([email protected]) using 'yarn add limiter'
Am I doing something wrong? Any help appreciated
Hi jhurliman have you thought of making your code suitable to run within the web browser, too? Thanks.
G.
Is it possible to have two limits?
For example, I want to limit 1 request per second, to a maximum of 1000 per hour.
Can I do that using this package?
Hi All,
Imagine I wanted to write a command line script in Node that needs to be rate-limited across multiple executions, e.g. a Twitter API client. I call it once, the limiter takes note of it, the script terminates. Then I call it again, the limiter is aware of the previous call, checks the limiting etc.
How do I manage this need with this library? E.g. can I "serialize" in some way the status of the library's objects, so that I can save them and re-load them back every time I run the script? I wouldn't want to use any backend component, as in the choice of Redis for classdojo/rolling-rate-limiter, but would be happy with something simpler and slower, such as file system or environment variables.
Thanks.
I'm going to keep digging into the code to see what's possible, but in the event that someone can provide a quick answer and save me some time, it seems worth asking the question. I have an API backed by ExpressJS. For a handful of endpoints, I need to ensure that those endpoints can't be accessed more than n times per hour per identifier.
In other words, it's not that the endpoint itself can't be hit more than 3 times an hour, but rather that it can't be hit more the 3 times an hour by any given [insert parameter]. In every case, the parameter is part of the request (e.g. req.body.identifier
).
Anything jump out to say that this can't be done?
I am confused after reading the example:
var RateLimiter = require('limiter').RateLimiter;
var limiter = new RateLimiter(150, 'hour', true); // fire CB immediately
// Immediately send 429 header to client when rate limiting is in effect
limiter.removeTokens(1, function(err, remainingRequests) {
if (remainingRequests < 1) {
response.writeHead(429, {'Content-Type': 'text/plain;charset=UTF-8'});
response.end('429 Too Many Requests - your IP is being rate limited');
} else {
callMyMessageSendingFunction(...);
}
});
Where is 'response' defined? Don't we need to hook the limiter to a path with app.use()? How does the limiter know the incoming IP otherwise? Also, how do we queue up the messages to ensure correct order?
If the computer's time changes, Date() will be affected, that's why a monotonic time should be used, such as process.hrtime.
I have been wrapping removeTokens
in a promise (as suggested in #52) for a while.
Feels like it would be clean and useful if we implemented an asyncRemoveTokens
method.
function asyncRemoveTokens(count: number, rateLimiter: RateLimiter) {
return new Promise((resolve, reject) => {
rateLimiter.removeTokens(count, (error, remainingRequests) => {
if (error) return reject(error)
resolve(remainingRequests)
})
})
}
This is what I was looking for however it can't currently be installed with npm since there is already such a package.
Hi,
I'd love for this library to have the following functionality, as it would help me a lot in the app I'm developing:
parent
parameter of TokenBucket
, in order for the RateLimiter
to obey both a per-second and per-hour limitRateLimiter
Would that be possible? If not, please let me know and I'll see if I can add them.
Thanks!
It looks like maybe the callbacks provided to removeTokens
can be called out of order. I'm not sure if ordering is an intended feature, but if not it'd be good call that out clearly in the docs.
var RateLimiter = require('limiter').RateLimiter;
var l = new RateLimiter(1, 10000);
var i = 0;
var f = () => {
var j = i++;
var d = new Date();
l.removeTokens(1, () => console.log(`${j}: enqueued: ${d}; dequeued: ${new Date()}`));
}
for (var k = 0; k < 5; k++) setTimeout(() => f(), 1000*k);
Running this code several times shows arbitrary ordering:
> i = 0
> for (var k = 0; k < 5; k++) setTimeout(() => f(), 1000*k);
0: enqueued: Thu Feb 01 2018 12:41:18 GMT-0800 (PST); dequeued: Thu Feb 01 2018 12:41:18 GMT-0800 (PST)
2: enqueued: Thu Feb 01 2018 12:41:20 GMT-0800 (PST); dequeued: Thu Feb 01 2018 12:41:28 GMT-0800 (PST)
1: enqueued: Thu Feb 01 2018 12:41:19 GMT-0800 (PST); dequeued: Thu Feb 01 2018 12:41:38 GMT-0800 (PST)
3: enqueued: Thu Feb 01 2018 12:41:21 GMT-0800 (PST); dequeued: Thu Feb 01 2018 12:41:48 GMT-0800 (PST)
4: enqueued: Thu Feb 01 2018 12:41:22 GMT-0800 (PST); dequeued: Thu Feb 01 2018 12:41:58 GMT-0800 (PST)
> i = 0
> for (var k = 0; k < 5; k++) setTimeout(() => f(), 1000*k);
0: enqueued: Thu Feb 01 2018 12:45:00 GMT-0800 (PST); dequeued: Thu Feb 01 2018 12:45:00 GMT-0800 (PST)
1: enqueued: Thu Feb 01 2018 12:45:01 GMT-0800 (PST); dequeued: Thu Feb 01 2018 12:45:10 GMT-0800 (PST)
3: enqueued: Thu Feb 01 2018 12:45:03 GMT-0800 (PST); dequeued: Thu Feb 01 2018 12:45:20 GMT-0800 (PST)
4: enqueued: Thu Feb 01 2018 12:45:04 GMT-0800 (PST); dequeued: Thu Feb 01 2018 12:45:30 GMT-0800 (PST)
2: enqueued: Thu Feb 01 2018 12:45:02 GMT-0800 (PST); dequeued: Thu Feb 01 2018 12:45:40 GMT-0800 (PST)
I can work around this limitation so this isn't an urgent issue for me. However if I'm misunderstanding something any pointers would be helpful. I have a slight suspicion that maybe setTimeout
doesn't quite work the way I think it does :)
This would make this module suited for clients wanting to ADHERE to a certain rate limit as well. This, as opposed to api's wanting to IMPOSE a rate limit, which is what this seems to be primarily targetted upon.
If there's an easy way to do this, I'll be glad to hear.
Thanks,
Geert-Jan
Currently, "type": "module"
is not supported well in webpack 5 or ts-node, in package.json
it already has "exports" and "module" fields set properly, so no need the "type": "module"
field.
same issue is also happening with the just-performance
#45 fixes a type declaration that can prevent typescript using this module from compiling, but the package has not been updated since this change.
Package should be updated to 1.1.3.
I used await
inside callMyRequestSendingFunction(...)
. Is it possible to use async function as callback?
As I tried adding async,
while (morePagesAvailable) {
let response:any = [];
myRateLimiter.removeTokens(1, async function(err, remainingRequests) {
response = await fetcher(url);
});
// Add response to the list of responses
}
Hello, I think it may be a good idea to include a non-leaky bucket implementation. Instead of it eventually doing the intended action, just block the action altogether.
In my case, I create the limiter object after the first call using the returned header.
So for now I create the object, then run a removeTokens function and ignore the result. It would be really fantastic if I could simply pass the remaining tokens for this interval into the constructor, that way I wouldn't have to make a simply removeTokens method with a blank callback.
Such as Limiter(120, 'minute', 119);
i=0;while (i < 125){ cb.socket.send('JOIN b4b3872292a3cf3905c14d4722ad25193f00f41c 2 {NS US-01}'); i++; }
Something as simple as this goes so fast, it bypasses the rate limiter even when set at no more than 2 requests per second.
I recommend using Date.now() and checking it against their last session request and closing the connection, and using this limiter script as well. Use both, but don't rely just on this script for protection.
I implement a queue to hold requests until the condition t remove a token holds true, and if it does I remove a token and proceed with my task. I define my bucket size to be 3, and the rate limit interval as 1 minute. Given below is the code I used initially to implement rate limiter.
Throttler.prototype.checkAndExecute = function ()
if (this.queue.length > 0)
if (this.limiter.getTokensRemaining() > 0)
if (this.limiter.tryRemoveTokens(1)) {
var job = that.queue.shift();
if (job) {
// do my job....
}
}
}
}
};
I wrap the above function inside an interval to check the queue contents and trigger the job if a token is available. Code given below -
this.peekInterval = setInterval( function() {
that.checkAndExecute();
}, 1000 );
The issue is that when I send 10 jobs to the queue, the first 3 are picked immediately by removing tokens from the bucket and executed. So, ideally, my request to remove 1 more token should not be allowed until at least 60 seconds have passed since I removed the first token. But, the bcuket gets filled up with another token in 20 seconds (60 seconds / 3), and the 4th job gets to execute in the 21st second. Thus, in the 1 minute interval since the first token was removed, more than 3 jobs got executed (in fact 5 jobs execute in the first minute itself), which violates the rate limit I set - 3 jobs per minute.
I am able to work around with this by modifying the checkAndExecute function in the way below -
Throttler.prototype.checkAndExecute = function () {
if (that.queue.length > 0) {
if (this.limiter.getTokensRemaining() >= this.bucketSize) {
if (this.limiter.tryRemoveTokens(1)) {
var job = that.queue.shift();
if (job) {
// do my job....
}
}
}
}
};
I'm not sure if anyone else has faced this scenario like I did, but just wanted to flag this out.
I understand that how the node rate-limiter and token-bucket functions holds good when a user wants to implement the token bucket algorithm, but in true definition of a rate limiter, the bucket should not be filled with additional tokens until the time interval since the removal of the 1st token has expired.
I use a new RateLimiter (1, 8000) and thought this means that I want to throttle up to a frequency of
1 every 8 seconds.
However, it seems to reduce it to once every second.
I think it is important to be able to select this higher number.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.