saeed
saeed

Reputation: 3923

throttle requests in Node.js

I have an array. I can loop over it with the foreach method.

data.forEach(function (result, i) {

     url = data[i].url;

     request(url);

});

The request function is making a http request to the given url. However making all these requests at the same time leads to all sorts of problems.

So I thought I should slow things down by introducing some-sort of timer.

But I have no idea how will be able to combine a forach loop with setTimeOut/setInterval

Please note am doing this on the server (nodejs) rather on the browser.

Thanks for you help.

Upvotes: 7

Views: 7052

Answers (8)

Edwin Joassart
Edwin Joassart

Reputation: 958

a very simple solution for quick throttling using async / await is to create a promise such as :

const wait = (delay) => new Promise((resolve, _) => {
  setTimeout(() => resolve(true), delay)
})

Then await for it when you need to pause (in an async function):

  //... loop process
  await wait(100)

Note: you need to use a for loop for this to work, a forEach won't wait.

Upvotes: 0

Edmilson Santana
Edmilson Santana

Reputation: 361

Some addings to this questions, just for knowledge base.

Created and async version without recursion.

function onceEvery(msTime) {
  return {
    doForEach: function (eachFunc, paramArray) {
      var i = 0, Len = paramArray.length;
      (function rekurse() {
        if (i < Len) {
          eachFunc(paramArray[i], i, paramArray);
          setTimeout(rekurse, msTime);
          ++i;
        }
      })();
    },
    doForEachAsync: async function (eachFunc, paramArray, staticParamenters) {
      var i = 0, Len = paramArray.length;
      while (i < Len) {
        await (async function rekurse() {
          await eachFunc(paramArray[i], staticParamenters);
          setTimeout(() => { }, msTime);
          ++i;
        })();
      }
    }
  };
}

module.exports = {
  onceEvery
};

Put this code in a .js and call as simple as:

await throttle.onceEvery(100).doForEachAsync(loadJobFunction, arrayParam, { staticParam1, staticParam2, staticParam3 });

Upvotes: 0

Jack G
Jack G

Reputation: 5322

Many of the above solutions, while practical for a few requests, unfourtunatly choke up and brick the page when dealing with tens of thousands of requests. Instead of queuing all of the timers at one, each timer should be qued sequentially one after another. If your goal is to have nice pretty fluffy code with lots of sugar and 'goodie-goodies' then below is the solution for you.

function miliseconds(x) { return x }
function onceEvery( msTime ){
    return {
        doForEach: function(arr, eachF){
            var i = 0, Len = arr.length;
            (function rekurse(){
                if (i < Len) {
                    eachF( arr[i], i, arr );
                    setTimeout(rekurse, msTime);
                    ++i;
                }
            })();
        }
    };
}

Nice, pretty, fluffy sugar-coated usage:

onceEvery(
    miliseconds( 150 )
).doForEach(
    ["Lorem", "ipsum", "dolar", "un", "sit", "amet"],
    function(value, index, array){
        console.log( value, index );
    }
)

function miliseconds(x) { return x }
function onceEvery( msTime ){
    return {
        doForEach: function(arr, eachF){
            var i = 0, Len = arr.length;
            (function rekurse(){
                if (i < Len) {
                    eachF( arr[i], i, arr );
                    setTimeout(rekurse, msTime);
                    ++i;
                }
            })();
        }
    };
}

Upvotes: 1

Bergi
Bergi

Reputation: 664196

As your problem is global, you should adjust your request function to have only 5 request running at a time - using a global, static counter. If your request was before something like

function request(url, callback) {
    ajax(url, callback);
}

now use something like

var count = 0;
var waiting = [];
function request(url, callback) {
    if (count < 5) {
        count++;
        ajax(url, function() {
            count--;
            if (waiting.length)
                request.apply(null, waiting.shift());
            callback.apply(this, arguments);
        });
    } else
        waiting.push(arguments);
}

Upvotes: 8

I Hate Lazy
I Hate Lazy

Reputation: 48761

Instead of setTimeout could have them run in sequence. I assume there's a callback parameter to your request() function.

function makeRequest(arr, i) {
    if (i < arr.length) {
        request(arr[i].url, function() { 
                                i++; 
                                makeRequest(arr, i); 
                            });
    }
}

makeRequest(data, 0);

If you need a little more time between requests, then add the setTimeout to the callback.

function makeRequest(arr, i) {
    if (i < arr.length) {
        request(arr[i].url, function() { 
                                i++; 
                                setTimeout(makeRequest, 1000, arr, i); 
                            });
    }
}

makeRequest(data, 0);

Upvotes: 3

Peter Olson
Peter Olson

Reputation: 142911

You can the offset the execution delay of each item by the index, like this:

data.forEach(function (result, i) { 
  setTimeout(function() {
    url = data[i].url; 
    request(url);
  }, i * 100);
}); 

This will make each iteration execute about 100 milliseconds after the previous one. You can change 100 to whatever number you like to change the delay.

Upvotes: 0

Mark Pieszak - Trilon.io
Mark Pieszak - Trilon.io

Reputation: 66921

data.forEach(function (result, i) {

     url = data[i].url;

     setTimeout(
         function () {
              request(url);
         }, 
         1000 * (i + 1) // where they will each progressively wait 1 sec more each
     );

 });

Upvotes: 5

Anoop
Anoop

Reputation: 23208

you can delay call using setTimeout. following code will insure that each request get called after timerMultiPlier milliseconds from its previous request.

var timerMultiPlier = 1000;
data.forEach(function (result, i) {
     setTimeout(function(){
           url = data[i].url;         
           request(url);
   }, timerMultiPlier*i );

});

Upvotes: 1

Related Questions