This is alpha quality, needs more tests.
Provides:
async function awaitn(n, generator, [onResolve, onReject, maxPerPeriod, timePeriodInMilliseconds])
Takes a generator the produces promises and windows at most N to be waited on calling onResolve
and onReject
for each promise.
If either of onResolve
or onRejected
return a value awaitn
will resolve to that value and stop.
This is mainly useful for limiting async code that talks to other services to ensure at most n
conversations are occuring at a
time; e.g. you can avoid swamping your db with lots of requests or an external API with too many requests.
Optionally takes maxPerPeriod
and timePeriodMilliseconds
(defaults to 1000) to rate limit the pulling of the promises
from the generator.
Rate limiting
It is (usually) better to lower the timePeriodInMilliseconds
and keep maxPerPeriod
at 1, as that will spread out the requests.
Also, the time based limitation is based on initiating the requests, not on the completion of the requests. So [1, 200]
would create at most 1 request every 200ms, and [5, 1000] would create 5 requests (immediately) and another batch of 5
requests are 1 second (assuming it didn't exceed n
requests in flight).
Deferred vs Direct generation of promises
Since a generator only returns done when trying to read the next element if we directly return promises we have to wait out the
rate limiter before calling the generator to determine if there are more promises to process. (Since the promise is scheduled
immediately.)
We can move the rate limiter to after calling the generator by having the generator return a function to produce the promise
(defer its generation) and only waiting if there is another promise to be processed.
Example Usage
// replace with actual request data
const requestsToMake = [ 'a', 'b', 'c', 'd', 'e', 'f', 'g' ];
// replace with actual function to process the requests
async function doRequest(r) { return Date.now() + ' r: ' + r; }
// converts an iterable to a deferred stream of applications of a function
function* dgmap(iterable, f) {
for (const n of iterable) {
yield () => f(n); // defer running f to the callers discretion
}
}
// at most 4 in flight at a time, max 1 request every 200ms.
awaitn(4, dgmap(requestsToMake, doRequest), console.log, console.error, 1, 200);