7

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.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
saeed
  • 3,861
  • 3
  • 25
  • 23
  • Not exactly what you want, but you can try queueing: https://github.com/caolan/async#queue. IE only do 10 requests at a time. – Jonathan Ong Sep 24 '12 at 19:41
  • What do you mean by "*all sorts of problems*"? – Bergi Sep 24 '12 at 19:41
  • @Bergi on my own nodejs server there is default limit of 5 request that can be made by any "agent". I could increase but I don't want to do that. For external servers they my cut you off if you make a large amount of concurrent requests. Thats what I mean by "all sort of problems". – saeed Sep 24 '12 at 19:46
  • @saeed: Does your `request()` function take a callback. If so, you don't need a `setTimeout` to handle the 5 request limit. Just run them in sequence, doing the next when the previous is finished. – I Hate Lazy Sep 24 '12 at 19:47
  • @user1689607 By that do mean continue execution after http response has been received. I thought that will more complicated to implement than the setting a timer. – saeed Sep 24 '12 at 19:53
  • @saeed: Yes, my answer below shows how. Execution of the next item in the Array will happen only after the previous one is done. It isn't really complicated. If you're going to use NodeJS, you're going to have to get used to working with asynchronous code and callbacks. Using a timer is clumsy. How do you know what the time should be set at? ;) – I Hate Lazy Sep 24 '12 at 19:56
  • @user1689607 yeah I know timers are lame. I just made request to my own server using a timer and i'm still running into limit 5. Socket hangs after 5 request. I am going to try your code now and do it the proper way. – saeed Sep 24 '12 at 20:07

8 Answers8

8

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);
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
5
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
     );

 });
Mark Pieszak - Trilon.io
  • 61,391
  • 14
  • 82
  • 96
3

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);
I Hate Lazy
  • 47,415
  • 13
  • 86
  • 77
1

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 );

});
Anoop
  • 23,044
  • 10
  • 62
  • 76
1

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;
                }
            })();
        }
    };
}
Jack G
  • 4,553
  • 2
  • 41
  • 50
0

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.

Peter Olson
  • 139,199
  • 49
  • 202
  • 242
0

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 });
Edmilson Santana
  • 176
  • 1
  • 10
0

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.

Edwin Joassart
  • 930
  • 1
  • 7
  • 19