0

My Node application sends invoice data to an API and I would like my application to send the data in series / wait a while after each invoice post, because the API server is blocking the requests if there are over 20 in a second.

I think one way would be making the for-loop pause after each iteration, but I am not sure how to achieve it.

Below is the code where I currently push the "invoice generation" -promises to an array in a for-loop and then resolve them with Promise.all.

All comments appreciated.

    // First, get access token from the service API
    serviceAPI.auth().then((token) => {

    // Array for the invoice promises
    let invoicePromises = [];

    // Push promises to the array
    for (let i = 0; i < rowAmount; i++) {
        invoicePromises.push(serviceAPI.generate(i, sheetData[i], token));
    }

    return Promise.all(invoicePromises);

    }).then((results) => {
    console.log(results);
    return utils.sortReport(results); // Return sorted results
    }).catch((e) => { console.error(e)});
Mulperi
  • 1,450
  • 1
  • 14
  • 24
  • 1
    Here's a way to actual meter out your requests for any particular number of requests/second: [Choose proper async method for batch processing with rate limiting](https://stackoverflow.com/questions/36730745/choose-proper-async-method-for-batch-processing/36736593#36736593) – jfriend00 Dec 30 '17 at 17:57
  • Keep in mind that efficiently and reliably managing to a number of requests/sec is not as simple as inserting a delay. An algorithm that reliably and efficiently keeps you just below a number of requests/second requires counting and timing requests and using timers to schedule when the next request can be sent. – jfriend00 Dec 30 '17 at 18:02
  • @jfriend00: Thanks for these wise words. I will study them hard now. – Mulperi Dec 30 '17 at 19:06
  • @jfriend00: Is it possible to recreate your function in Node or do I really need synchronous flow with async library or something like that? Your example helped to grasp the idea but I am struggling to make it work in Node. – Mulperi Jan 02 '18 at 06:10
  • My `rateLimitMap()` function works just fine in node.js. To use it, you just need to have an array you want to iterate, have a function that accepts an array element as an argument and returns a promise and then copy the `rateLimitMap()` function. You can just stub out the `update()` function to do nothing or remove the calls to it from the `rateLimitMap()` function as those are just designed to show progress in the demo. Everything else is just generic Javascript that runs anywhere. – jfriend00 Jan 02 '18 at 06:13

2 Answers2

2

Instead of creating them one at all:

 for (let i = 0; i < rowAmount; i++) {
    invoicePromises.push(serviceAPI.generate(i, sheetData[i], token));
}
return Promise.all(invoicePromises);

One could build up a Promise chain that calls one after another:

 sheetData.reduce((promise, data, i) => promise.then(result => serviceAPI.generate(i, data, token).then(res => result.concat(res))), Promise.resolve([]));

However thats kind of Promise hell, so lets use async await instead:

return (async function(){
  const result;
  for (let i = 0; i < rowAmount; i++) {
    result.push(await serviceAPI.generate(i, sheetData[i], token));
   //optionally sth like await timer(1000);
  } 
 return result;
})();
Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
  • Thank you! I will try these. – Mulperi Dec 30 '17 at 17:38
  • This sequences one at a time. That isn't even remotely the same as 20 requests/second. If a given request takes 5ms, then this will launch 200 requests per second (way too many requests). If a given request takes 5 seconds, then this will launch only one request every 5 seconds (way too few requests). So, this doesn't really measure what needs to be measured to be efficient and safely stay under the max requests/sec. – jfriend00 Dec 30 '17 at 17:59
  • @jfriend00 okay then https://stackoverflow.com/questions/47967232/how-to-make-a-promise-resolve-with-a-specific-condition/47967532#47967532 – Jonas Wilms Dec 30 '17 at 20:12
-2

To make a nodejs app wait for a bit and then go on, you could do this easily using the sleep module.

first: npm install sleep --save

then add this to to the require section in your code: const sleep = require('sleep');

and then in your for loop: sleep.sleep(5); // sleep for 5 seconds sleep.msleep(5); // sleep for 5 milliseconds

Bob Dill
  • 1,000
  • 5
  • 13
  • Wow that was easy, thanks. The npm gives a warning about something called _ForceSet_ that is deprecated in the module. Don't know if it's relevant though. – Mulperi Dec 30 '17 at 17:37
  • 2
    @Mulperi - A simple delay is not really the best way to implement 20 requests/second. To do it the right way, you need to keep track of how many requests have been sent in the last second and stop sending when it gets to 20, set a timer for when you are allowed to send a new one, etc... Doing it this will can get you under the limit by over compensating, but it's not the fastest way to stay under the limit. – jfriend00 Dec 30 '17 at 17:55
  • Also, if this is https://www.npmjs.com/package/sleep, then it blocks the entire node.js event loop which is really bad. You can't process any other requests that might arrive while sleeping. It essentially "hangs" your entire server for the sleep duration. Mainly useful for debugging only. – jfriend00 Dec 30 '17 at 18:14
  • @jfriend00: any advice how to keep track of the time? Using node-timers or something like that? – Mulperi Dec 30 '17 at 19:02
  • @Mulperi - Did you look at [the post I linked in a comment to your question](https://stackoverflow.com/questions/36730745/choose-proper-async-method-for-batch-processing/36736593#36736593)? That shows how to calculate requests/second in that code. I posted that reference over an hour ago and you haven't said anything about it. That specific code likely solves your problem and certainly illustrates how to do this kind of work. Please read that answer and study that code. – jfriend00 Dec 30 '17 at 19:02