Search code examples
javascriptnode.jsasync-awaitrate-limiting

async/await with Limiter for sending requests


I'm trying to limit the number of requests I send to an API.

I'm using Limiter and it's working just like I need, the only issue is that I can't find a way to use it with await (I need all the responses before rendering my page)

Can someone give me a hand with it?

Btw the Log returns a boolean.

const RateLimiter = require('limiter').RateLimiter;

const limiter = new RateLimiter(50, 5000)

for (let i = 0; i < arrayOfOrders.length; i++) {
    const response = limiter.removeTokens(1, async (err, remainingRequests) => {
        console.log('request')
        return await CoreServices.load('updateOrder', {
            "OrderNumber": arrayOfOrders[i],
            "WorkFlowID": status
        })
    })
    console.log('response', response)
}

console.log('needs to log after all the request');

this is loggin:

response true
response true
response false
needs to log after all the request
request
request
request
...

Solution

  • Promisifying .removeTokens will help, see if this code works

    const RateLimiter = require('limiter').RateLimiter;
    
    const limiter = new RateLimiter(50, 5000);
    
    const tokenPromise = n => new Promise((resolve, reject) => {
        limiter.removeTokens(n, (err, remainingRequests) => {
            if (err) {
                reject(err);
            } else {
                resolve(remainingRequests);
            }
        });
    });
    (async() => { // this line required only if this code is top level, otherwise use in an `async function`
        const results = await Promise.all(arrayOfOrders.map(async (order) => {
            await tokenPromise(1);
            console.log('request');
            return CoreServices.load('updateOrder', {
                "OrderNumber": order,
                "WorkFlowID": status
            });
        }));
        console.log('needs to log after all the request');
    })(); // this line required only if this code is top level, otherwise use in an `async function`
    

    explanation

    Firstly:

    const tokenPromise = n => new Promise((resolve, reject) => {
        limiter.removeTokens(n, (err, remainingRequests) => {
            if (err) {
                reject(err);
            } else {
                resolve(remainingRequests);
            }
        });
    });
    

    promisifies the limiter.removeTokens to use in async/await - in nodejs you could use the built in promisifier, however lately I've had too many instances where that fails - so a manual promisification (I'm making up a lot of words here!) works just as well

    Now the code is easy - you can use arrayOfOrders.map rather than a for loop to create an array of promises that all run parallel as much as the rate limiting allows, (the rate limiting is done inside the callback)

    await Promise.all(... will wait until all the CoreServices.load have completed (or one has failed - you could use await Promise.allSettled(... instead if you want)

    The code in the map callback is tagged async so:

    await tokenPromise(1);
    

    will wait until the removeTokens callback is called - and then the request

    return CoreServices.load
    

    is made

    Note, this was originally return await CoreServices.load but the await is redundant, as return await somepromise in an async function is just the same as return somepromise - so, adjust your code too