2

I have some nested Node.js forEach loops which are hitting API rate limiters and I would like to slow them down so that they are not hitting the rate limit. I have tried adding a setTimeout on the end of the loop but that has no impact at all on the speed of the requests. Ideally, I would be able to send a request every 2 seconds. How can I achieve this? Everything online seems to talk about rate-limiting an API but nothing on the sending side of things.

joshk132
  • 1,011
  • 12
  • 37
  • Please show some code of where you're calling the API wihin a loop – Jamiec Jan 26 '22 at 16:27
  • This question should answer your question: https://stackoverflow.com/questions/37977602/settimeout-not-working-inside-foreach – Sorin Jan 26 '22 at 16:34
  • @Jamiec I don't think that would be of help too much past linking to what a forEach loop looks like. This is a general question about rate-limiting a forEach loop, not an exact rate limit my API. I have other places in my app that I would like to limit a forEach loop too so no point in a specific solution when I need general for multiple use cases – joshk132 Jan 26 '22 at 16:35

2 Answers2

2

To delay in a loop, first of all, your function must be async.

You can then await a promise that resolves after 2 seconds. Such a delay can be inserted as a one-liner like this:

await new Promise(resolve => setTimeout(resolve, 2000));

...or as a handy function like:

const delay = time => new Promise(resolve => setTimeout(resolve, time));

...which can then be used in a loop like:

async function fetchEverything() {
   for (let thing of things) {
      await fetchThing(thing);
      await delay(2000);
   }
}
Wyck
  • 10,311
  • 6
  • 39
  • 60
  • If you're seeking guidance with `forEach` specifically, please see [this question](https://stackoverflow.com/questions/37576685/using-async-await-with-a-foreach-loop) – Wyck Jan 26 '22 at 17:04
-1

You can make a generic extension on Array which can insert a delay in between each iteration, you will want to make the function async so you can await it (and therefore know when it's finished).

Array.prototype.forEachLimited = async function(fn, timeBetweenCallsMs){
    
    const delay = async () => new Promise(resolve => setTimeout(resolve, timeBetweenCallsMs));
    
    for(var i=0;i<this.length;i++){
      fn(this[i]);
      await delay();
    }
};


(async function(){
  var myArr = [1,2,3,4,5];
  await myArr.forEachLimited(i => console.log(i), 2000);
  console.log("Finished");
})();

Some may frown at adding this to the Array.prototype, it will work just as well as a normal function taking the array as an argument, so the same thing without adding it to Array.prototype

async function forEachLimited(arr, fn, timeBetweenCallsMs) {

  const delay = async() => new Promise(resolve => setTimeout(resolve, timeBetweenCallsMs));

  for (var i = 0; i < arr.length; i++) {
    fn(arr[i]);
    await delay();
  }
};


(async function() {
  var myArr = [1, 2, 3, 4, 5];
  await forEachLimited(myArr, i => console.log(i), 2000);
  console.log("Finished");
})();
Jamiec
  • 133,658
  • 13
  • 134
  • 193
  • Downvoter wish to comment? – Jamiec Jan 26 '22 at 16:48
  • I would suggest that the downvote has to do with the mutation of the prototype. It's considered poor form by some to touch the prototype because if a function is ever added by the vendor that matches yours, it could cause unpredictable behaviour. Either in your code or in 3rd party libraries that expect that function to behave in specific ways. Probably didn't scroll down to see the non mutation example or still downvoted it on principal. – azariah Feb 07 '22 at 05:50