Why do they say that recursive setTimeout is better than setInterval?

Asked

Viewed 1,922 times

42

I see online a series of articles with titles like: setInterval is bad, which translating would be setInterval is ruin.

I saw recommendations to use an anonymous recursive function with a setTimeout, for being apparently better than the use of setInterval

Exemplifying setInterval:

setInterval(function (){
    $('#horas').html((new Date).toLocaleString().substr(11))
}, 1000);

Exemplifying with recursive function and setTimeout:

(function getHours()
{
    $('#horas').html((new Date).toLocaleString().substr(11))
    setTimeout(getHours, 1000);

}());

Why do they say that?

Example in JSFIDDLE

2 answers

37


The problem that leads to considering recursion is that the setInterval is "blind". It runs regardless of whether the asynchronous code inside has completed or not, has failed or not. Using setTimeout recursive is a better method with more control.

A typical example is an Ajax request that has to be executed from time to time. With setInterval can happen that the requests and answers arrive with order exchanged, as it is not guaranteed that they arrive sequentially. Doing the same with setTimeout can be checked first that the answer arrived and then send new request.

Another important aspect is that setInterval runs the code even though the previous execution gave an error. This can cause chain errors and create a lot of junk in the log. The setTimeout does not behave like this, it stops if there are errors in the script.

Another example is if the synchronous code takes longer to execute than the waiting interval of the setInterval. In this case the Browser expects the code inside the setInterval run, and then start the new run. This can lead to problems if you are counting on the code to take x time.

Example: http://jsfiddle.net/z7vb46p5/. In this example the setInterval receive a half-second interval, but in practice it takes longer, waiting for the synchronous code to run.

You can read about this also on MDN, which recommends using recursivity via setTimeout in cases where the code to be executed lasts longer than the interval itself (as mentioned above):

If there is a possibility that your Logic could take longer to execute than the interval time, it is Recommended that you recursively call a named Function using window.setTimeout. For example, if using setInterval to Poll a remote server Every 5 Seconds, network Latency, an unresponsive server, and a host of other issues could Prevent the request from completing in its alloted time. As such, you may find yourself with queued up XHR requests that won’t necessarily Return in order.

For such cases, a recursive setTimeout Pattern is Preferred

There are clear cases where the setInterval is simpler, and should be used, but is not always the best solution. Long intervals, or to make simple code constant updates, setInterval may even be the best solution.

Note: When it is intended to have a repeat action in very short span of time in the Browser, then it may be better to use the requestAnimationFrame which is a function that the browser provides to run a function the next time the browser has free.

  • 1

    If you need any additional reading: http://reallifejs.com/brainchunks/repeated-events-timeout-interval/

  • In the case of a long polling, should use the "chaining with setTimeout, okay?

  • @Wallacemaxters I would do with setTimeout yes.

31

I’m always suspicious of "X is better than Y" type statements, or "X is evil". They usually have foundation, but should not be followed blindly. The ideal is to understand why these statements exist, and know how to use the right tool at the right time. Javascript timers are no different.

The short answer is: the two methods are equivalent when you are scheduling synchronous operations. When you schedule asynchronous operations, such as query data on the server, use of the setInterval may cause unwanted effects depending on what is done in the callback of asynchronous operation.

Now the long explanation :)

How timers work in Javascript

Javascript timers are not accurate. When you give a timer a range of n milliseconds, what happens is that your callback will run on at least n milliseconds. This is a consequence of the way the language is implemented, with a single thread and a event loop controlling the execution flow of asynchronous operations.

At the beginning of each cycle of this event loop, the outstanding asynchronous operations are handled. For example, if you have been given a mouse click on a particular element that you have Event Listener, this Listener runs, and blocks the thread until its execution.

With timers it is the same thing: if at the beginning of a cycle an expired timer is detected, your callback will run. This callback will block the execution of any other code until you have completed your tasks.

setInterval and setTimeout

Taking into account the execution model explained above, consider this code:

// agenda func para executar em 100ms
setTimeout(func, 100);

// operação que demora 150ms
fazOperacaoLenta();

This executes according to the following chronology:

        tempo:  0ms            100ms    150ms
                |                |        |
----------------+----------------+--------+------>
ciclo do loop   |                X        |
   de eventos:  1                         2
     
                fazOperacaoLenta()        func()

The function that had been scheduled for 100ms ahead had to wait longer, 150ms, because the synchronous code of the previous cycle took that long to finish executing.

With setInterval, this same delay occurs, which accumulates and can prevent the callback perform at certain times when he would be expected:

// agenda função lenta para executar em 100ms
setInterval(fazOperacaoLenta, 100);

// operação que demora 150ms
fazOperacaoLenta();

This executes according to the following chronology:

        tempo:  0ms            100ms    150ms    200ms            300ms
                |                |        |        |                |
----------------+----------------+--------+--------+----------------+----->
ciclo do loop   |                X        |        X                |
   de eventos:  1                         2                         3
     
                fazOperacaoLenta()        fazOperacaoLenta()        fazOperacaoLenta()

Note that in this period of 300ms it was for the function to have executed 4 times (one immediately, and 3 scheduled every 100ms), but only executed 3 times. The planned execution for the 100ms ran at 150ms. When arriving in the 300ms, realize that there was a pipeline: there would be two forecasted executions, the 200ms and the 300ms, but one of them ends up being discarded, and the function performs only once. (There will be people saying the opposite on the Internet, including Soen; do not believe!)

Timers that trigger synchronous operations

In the code snippets you put in the question, the scheduled operation (updating an DOM element to the current time) is synchronous. It immediately executes every call of the scheduled function, and blocks the execution of any other code until your work (update the DOM) is finished.

In such cases there is no difference between using setInterval or multiple calls linked to setTimeout. The behaviour in both cases will be identical.

Note: I’d rather say "chained" than recursive in this case. Technically, in recursion a function calls itself, and this affects the call stack, which increases until the recursion is stopped. In your code example, a function is executed and schedules the next execution of itself for the future. This is an asynchronous operation, and does not affect the call stack.

Timers that trigger asynchronous operations

This story that the setInterval is "evil" comes from its use to trigger asynchronous operations, especially queries to a remote server, which responds with data that needs to be processed and/or entered into the DOM. In fact, if you want to update a page element with server data every, say, half a second, the most naive approach is to use the setInterval to trigger server requests every 500ms. But this can cause problems, such as out-of-order responses as already mentioned in @Sergio’s reply, or receiving two responses very close to each other (one arrived "late", or the other "early").

The callbacks ajax requests are executed at the first opportunity (loop cycle of events) after the server responds with the data solved (or with an error). However, the response time of the server can vary from one request to the other, depending on factors totally out of our control, such as traffic, and others even more or less controllable, such as CPU load or machine memory. But there is no guarantee that, when sending a sequence of requests, the answers will arrive in the same order. So, if your need is to have a request queue, with handled responses in order, you need to submit a new request only after the previous reply has arrived.

Completion

I believe that the myth that one should not never use setInterval comes from this case of asynchronous request queue, which is something very common for people to want to implement. Really in this case it is best to chain calls to setTimeout. Maybe for those who don’t know what they’re doing is a good recommendation to always use the setTimeout. But if you know what you’re doing - and the goal of my long answer was to try to help you know -, it has how to use the right tool at the right time.

  • 1

    That’s it. If it’s in language, it’s for some reason. Let’s not discriminate or generalize things.

  • In the example given by Wallace the choice between the setInterval and the setTimeout makes no difference?

  • @Orion No, because the function that timers call does not trigger any asynchronous operation (it was scheduling itself in the case of setTimeout).

  • @bfavaretto your answer is excellent, my congratulations! I really liked the approach of knowing how to use the tool for the purpose for which it was created. But in the excerpt: "There will be people saying the opposite on the Internet, including on Soen; don’t believe it!" why should I believe you instead of them?

  • 2

    @Jedaias You’re right, I need to review the answer and include sources.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.