-1

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
...
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 1
    In your case `.map()` is going to return an array of promises. If you want to wait for all those to be done, then you will need to do `await Promise.all(arrayOfOrders.map(...))`. Or, if you're OK with sequencing them, change the `.map()` to a regular `for` loop and then the `await` inside the loop will actually do something useful. Keep in mind that `.map()` is not promise or `await` aware so `return await Coreservices.load(...)` isn't waiting for anything. It's just returning a promise to `.map()`. – jfriend00 Sep 21 '20 at 23:52
  • that's not the issue, using for it's not working either. It's still not waiting for all the request to be finished to continue. I believe it's something with limiter. – Pedro Zancopé Sep 22 '20 at 00:02
  • updated the code with the idea from @jfriend00 – Pedro Zancopé Sep 22 '20 at 00:06
  • I don't know what `limiter.removeTokens()` is supposed to be doing, but you're doomed to control the flow if you're mixing promises and regular callbacks. You can't control it. I guess you can promisify `limiter.removeTokens()` and then `await` that result. – jfriend00 Sep 22 '20 at 00:11
  • the for loop doesn't wait for "inner" asynchronous code ... you need to understand that the code in the for loop is probably running correctly (limited) - the console log doesn't mean it's not working, so there's nothing wrong with the limiter – Jaromanda X Sep 22 '20 at 00:12
  • the limiter is working perfectly, but as I need to render the responses on a page, I must wait for all them before continuing. – Pedro Zancopé Sep 22 '20 at 00:14
  • @jfriend00 limiter.removeTokens returns a boolean right away, so promisy it is not an option I guess. – Pedro Zancopé Sep 22 '20 at 00:16
  • How can it return a boolean right away when you're feeding it a promise? I don't get it. If it just returns a boolean, then you have no way of waiting for all the promises. I guess I don't know what problem you're trying to solve any more. – jfriend00 Sep 22 '20 at 00:19
  • the value returned by `limiter.removeTokens` is described as follows: `@returns {Boolean} True if the callback was fired immediately, otherwise false` but your code (and **all** example code in the library itself) doesn't use the return value so it's irrelevant to your issue – Jaromanda X Sep 22 '20 at 00:24
  • got you @JaromandaX! added only for making it clear it does not returns a promise – Pedro Zancopé Sep 22 '20 at 00:33
  • 1
    Check the answer - I think it'll do what you need – Jaromanda X Sep 22 '20 at 00:45

1 Answers1

1

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

Jaromanda X
  • 53,868
  • 5
  • 73
  • 87
  • Wow! thanks a lot man. worked perfectly, exactly what I needed. If don't mind, try explaining what you did so I don't bother you again next time hehe. – Pedro Zancopé Sep 22 '20 at 00:53
  • 1
    @PedroZancopé - added some explanation - note ... I did change the code, removed the await in `return await CoreServices.load` as the await there is redundant – Jaromanda X Sep 22 '20 at 01:16
  • @PedroZancopé also, changed the code at `"OrderNumber": arrayOfOrders[i],` as that was wrong - it should be `"OrderNumber": order,` - which I see you already picked up on!! Thanks – Jaromanda X Sep 22 '20 at 01:18