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.
If you need any additional reading: http://reallifejs.com/brainchunks/repeated-events-timeout-interval/
– Tobias Mesquita
In the case of a
long polling
, should use the "chaining withsetTimeout
, okay?– Wallace Maxters
@Wallacemaxters I would do with setTimeout yes.
– Sergio