74

For a simple web app that needs to refresh parts of data presented to the user in set intervals, are there any downsides to just using setInterval() to get a JSON from an endpoint instead of using a proper polling framework?

For the sake of an example, let's say I'm refreshing the status of a processing job every 5 seconds.

Audwin Oyong
  • 2,247
  • 3
  • 15
  • 32
Sologoub
  • 5,312
  • 6
  • 37
  • 65
  • 10
    I would use `setTimeout` and always call it when the previous response was received. This way you avoid possible congestion or function stacking or whatever you want to call it. – Felix Kling Dec 30 '11 at 18:22
  • I've done it just like @FelixKling said and works like a charm. Try it! – Alfabravo Dec 30 '11 at 18:28
  • Awesome! Can @FelixKling, could you please post that as the answer and I'll accept? – Sologoub Dec 30 '11 at 18:31

5 Answers5

93

From my comment:

I would use setTimeout [docs] and always call it when the previous response was received. This way you avoid possible congestion or function stacking or whatever you want to call it, in case a request/response takes longer than your interval.

So something like this:

function refresh() {
    // make Ajax call here, inside the callback call:
    setTimeout(refresh, 5000);
    // ...
}

// initial call, or just call refresh directly
setTimeout(refresh, 5000);
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • 3
    A nice analysis here on why avoiding `setInterval` is a good idea: http://weblogs.asp.net/bleroy/archive/2009/05/14/setinterval-is-moderately-evil.aspx – Dave Ward Dec 30 '11 at 18:39
  • should we use `setTimeout(refresh, 5000);` twice? I mean one after the initial call and then again in the function being called? – David Apr 28 '17 at 14:38
  • @David, it's because setTimeout doesn't repeat on an interval: it fires the function passed in to its first argument once after the amount of time in the second argument expires (really that time after the function is pushed onto the even queue, but I digress.) Hence, you need both calls: the first to kick off the function and the second to repeat until finished. – phillyslick Jun 28 '18 at 19:15
  • why don't you call clearTimeout() in the recursice rerfresh()? – daniel gi May 07 '20 at 15:21
  • @danielgi: What's the point? At the time `refresh` is called the timeout is "done". There is nothing to clear. – Felix Kling May 08 '20 at 15:10
  • 2
    Will there be more allocations to stack happening here, because it's a poll inside another poll inside another poll, toooo many recursive functions? – Himanshu Shekhar Dec 26 '21 at 08:50
  • 1
    setTimeout can block the code if the request never finishes. – france1 Oct 31 '22 at 13:01
  • It's worth noting that this solution can result in race conditions if your app requires that the loop be stoped with clearTimeout, and clearTimeout is called in the middle of your ajax call if you're using async/await. In this niche case you'll need to implement an extra check to see if the loop was stopped. – NerdSoup Jul 14 '23 at 20:31
21

A simple non-blocking poll function can be implemented in recent browsers using Promises:

var sleep = duration => new Promise(resolve => setTimeout(resolve, duration))
var poll = (promiseFn, duration) => promiseFn().then(
             sleep(duration).then(() => poll(promiseFn, duration)))

// Greet the World every second
poll(() => new Promise(() => console.log('Hello World!')), 1000)
bschlueter
  • 3,817
  • 1
  • 30
  • 48
  • 2
    good. works. If after some time I decide to stop the execution with a button click, for example, how could I do that, please? – Marcel Feb 15 '18 at 18:10
  • @Marcel Thanks. The short answer is that that isn't possible (https://stackoverflow.com/a/29479435/1054423). – bschlueter Feb 15 '18 at 20:41
  • Another promisified solution: https://morioh.com/p/2e1c6c90f85a – thdoan May 16 '23 at 00:49
6

You can do just like this:

var i = 0, loop_length = 50, loop_speed = 100;

function loop(){
    i+= 1; 
    /* Here is your code. Balabala...*/
    if (i===loop_length) clearInterval(handler);
}

var handler = setInterval(loop, loop_speed);
Scen
  • 921
  • 10
  • 14
2

Just modify @bschlueter's answer, and yes, you can cancel this poll function by calling cancelCallback()

let cancelCallback = () => {};

var sleep = (period) => {
  return new Promise((resolve) => {
    cancelCallback = () => {
      console.log("Canceling...");
      // send cancel message...
      return resolve('Canceled');
    }
    setTimeout(() => {
      resolve("tick");
    }, period)
  })
}

var poll = (promiseFn, period, timeout) => promiseFn().then(() => {
  let asleep = async(period) => {
    let respond = await sleep(period);
    // if you need to do something as soon as sleep finished
    console.log("sleep just finished, do something...");
    return respond;
  }


  // just check if cancelCallback is empty function, 
  // if yes, set a time out to run cancelCallback()
  if (cancelCallback.toString() === "() => {}") {
    console.log("set timout to run cancelCallback()")
    setTimeout(() => {
      cancelCallback()
    }, timeout);
  }

  asleep(period).then((respond) => {
    // check if sleep canceled, if not, continue to poll
    if (respond !== 'Canceled') {
      poll(promiseFn, period);
    } else {
      console.log(respond);
    }
  })

  // do something1...
  console.log("do something1...");

})


poll(() => new Promise((resolve) => {
  console.log('Hello World!');
  resolve(); //you need resolve to jump into .then()
}), 3000, 10000);

// do something2...
console.log("do something2....")
allenyllee
  • 964
  • 1
  • 13
  • 16
1

I know this is an old question but I stumbled over it, and in the StackOverflow way of doing things I thought I might improve it. You might want to consider a solution similar to what's described here which is known as long polling. OR another solution is WebSockets (one of the better implementations of websockets with the primary objective of working on all browsers) socket.io.

The first solution is basically summarized as you send a single AJAX request and wait for a response before sending an additional one, then once the response has been delivered, queue up the next query.

Meanwhile, on the backend you don't return a response until the status changes. So, in your scenario, you would utilize a while loop that would continue until the status changed, then return the changed status to the page. I really like this solution. As the answer linked above indicates, this is what facebook does (or at least has done in the past).

socket.io is basically the jQuery of Websockets, so that whichever browser your users are in you can establish a socket connection that can push data to the page (without polling at all). This is closer to a Blackberry's instant notifications, which - if you're going for instant, it's the best solution.

Community
  • 1
  • 1
th3byrdm4n
  • 172
  • 1
  • 10
  • Although socket.io might be good it is not THE solution for websockets just a solution among many. All of them (almost) good in their own way and you might consider different frameworks depending on the task to solve. – Uffe Jun 13 '13 at 20:28
  • Careless, updated to reflect the intention of mentioning socket.io – th3byrdm4n Jun 14 '13 at 23:07