glasspill
glasspill

Reputation: 1300

setTimeout in Node.js loop

I'm a bit confused as to how setTimeout works. I'm trying to have a setTimeout in a loop, so that the loop iterations are, say, 1s apart. Each loop iteration makes an HTTP request and it seems like the server on the other end can't handle that many requests in such a short time span.

for (var i = 1; i<=2000 && ok; i++) {
    var options = {
        host:'www.host.com',
        path:'/path/'+i
    };

    setTimeout(makeRequest(options, i), 1000);
};

Why does this not work and how can I achieve this?

Thank you

Upvotes: 16

Views: 32396

Answers (8)

I'm very late on the subject (as usual ...;) but the only way I found to loop requests to a slow time response API and getting responses without HTTP 504 is using promises.

async function LoadDataFromAPI(myParametersArray) {
    for(var i = 0; i < myParametersArray.length; i++) { 
        var x = await RunOneRequest(myParametersArray[i]); 
        console.log(x); // ok
    }
}

The function called by the async function :

function RunOneRequest(parameter) {
    return new Promise(resolve => {
        setTimeout(() => {
            request(parameter, (error, response, body) => {
                // your request
            });
        resolve('ok);
        }, 2000); // 2 secs
    });
}

Upvotes: 0

Harshan Morawaka
Harshan Morawaka

Reputation: 739

        let i = 20;
        let p = Promise.resolve(i)
        while (i > 0) {
        (i => {
            p = p.then(() => {
            return new Promise(function (resolve, reject) {
                setTimeout(function () {
                    console.log(i);
                resolve()
                }, 2000)
            })
            })
        })(i)
        i--
        }
        p = p.then(data => console.log('execution ends'))

Upvotes: 0

Sean
Sean

Reputation: 2529

I'm surprised that no one has mentioned this above, but it sounds like you need setInterval not setTimeout.

vat poller = setInterval(makeRequestFunc, 3000)

The code above will make a request every 3 seconds. Since you saved the object to the variable poller, you can stop polling by clearing the object like so:

cleanInterval(poller)

Upvotes: 2

Ahmad Maleki
Ahmad Maleki

Reputation: 1493

I might be late at the party but here is another (more readable) solution without the need to omit for loop.

What your code does is creating 2000 (actually 1999) setTimeout objects that will call the makeRequest function after 1 second from now. See, none of them knows about the existence of the other setTimeouts.

If you want them 1 sec apart from each other, you are responsible for creating them so.

This can be achieve by using your counter (in this case i) and the timeout delay.

for (var i = 1; i<=2000 && ok; i++) {
    var options = {
        host:'www.host.com',
        path:'/path/'+i
    };

    setTimeout(makeRequest(options, i), i * 1000); //Note i * 1000
};

The first timeout object will be set for 1 second from now and the second one will be set for 2 seconds from now and so on; Meaning 1 second apart from each other.

Upvotes: 3

Yves M.
Yves M.

Reputation: 31006

setTimeout is non blocking, it is asynchronous. You give it a callback and when the delay is over, your callback is called.

Here are some implementations:

Using recursion

You can use a recursive call in the setTimeout callback.

function waitAndDo(times) {
  if(times < 1) {
    return;
  }

  setTimeout(function() {

    // Do something here
    console.log('Doing a request');

    waitAndDo(times-1);
  }, 1000);
}

Here is how to use your function:

waitAndDo(2000); // Do it 2000 times

About stack overflow errors: setTimeout clear the call stack (see this question) so you don't have to worry about stack overflow on setTimeout recursive calls.

Using generators (io.js, ES6)

If you are already using io.js (the "next" Node.js that uses ES6) you can solve your problem without recursion with an elegant solution:

function* waitAndDo(times) {
  for(var i=0; i<times; i++) {

    // Sleep
    yield function(callback) {
      setTimeout(callback, 1000);
    }    

    // Do something here
    console.log('Doing a request');
  }
}

Here is how to use your function (with co):

var co = require('co');

co(function* () {
  yield waitAndDo(10);
});

BTW: This is really using a loop ;)

Generator functions documentation.

Upvotes: 16

jmar777
jmar777

Reputation: 39679

Right now you're scheduling all of your requests to happen at the same time, just a second after the script runs. You'll need to do something like the following:

var numRequests = 2000,
    cur = 1;

function scheduleRequest() {
    if (cur > numRequests) return;

    makeRequest({
        host: 'www.host.com',
        path: '/path/' + cur
    }, cur);

    cur++;
    setTimeout(scheduleRequest, 1000)
}

Note that each subsequent request is only scheduled after the current one completes.

Upvotes: 3

bart s
bart s

Reputation: 5100

You need something like this

var counter = 5;

function makeRequst(options, i) {
    // do your request here
}

function myFunction() {
    alert(counter);

    // create options object here
    //var options = {
    //    host:'www.host.com',
    //    path:'/path/'+counter
    //};
    //makeRequest(options, counter);

    counter--;
    if (counter > 0) {
        setTimeout(myFunction, 1000);    
    }
}

See also this fiddle

At the point of the alert(count); you can do your call to the server. Note that the counter works opposite (counting down). I updated with some comments where to do your thing.

Upvotes: 8

Evan Knowles
Evan Knowles

Reputation: 7511

You're calling makeRequest() in your setTimeout call - you should be passing the function to setTimeout, not calling it, so something like

setTimeout(makeRequest, 1000);

without the ()

Upvotes: 1

Related Questions