Javascript - Usando temporizadores like a Ninja

Você provavelmente já conhece os métodos setInterval() e setTimeout(). Mas você sabe a exatamente a diferença entre eles? Sabe como eles realmente funcionam? Vem descobrir :)

setInterval() e setTimeout() são funções temporizadoras. Elas executam uma função de callback após determinado tempo.

O uso delas é assim:

1
2
3
4
5
6
7
setTimeout( function() {
console.log( 'Executa uma vez após 1 segundo.' );
}, 1000 );

setInterval( function() {
console.log( 'Executa infinitamente, 1 vez por segundo.' );
}, 1000 );

Como você pode ver, a sintaxe para as duas é a mesma: o primeiro parâmetro é uma função de callback que será executada após x milissegundos, passados no segundo parâmetro.

A principal diferença entre elas é que:

  • setInterval() executa infinitamente, até que você execute clearInterval();
  • setTimeout() executa apenas uma vez.

Você pode remover um temporizador, atribuindo ele a uma variável, e depois usando clearInterval( variavel ) para o setInterval(), e clearTimeout( variavel ) para setTimeout().

Um exemplo prático:

1
2
3
4
5
6
7
8
var counter = 0;
var timer = setInterval(function() {
if( counter >= 10 ) {
clearInterval( timer );
}

console.log( counter++ );
}, 1000);

Colando esse código no seu console, você pode constatar que é printado o valor de 0 a 10, e então o temporizador é removido.

Para usar com o setTimeout(), poderíamos fazer algo assim:

1
2
3
4
5
6
7
8
9
10
11
var counter = 0;
function myTimer() {
var timer = setTimeout( function() {
console.log( counter++ );
if( counter < 11 ) {
myTimer();
}
}, 1000 );
}

myTimer();

Como o setTimeout() executa somente uma vez, precisamos utilizar uma função recursiva para que temporizador continue executando até que ele encha o saco e nós possamos mandá-lo parar de chamar a função! xD

Bom, até aqui, provavelmente você já sabia de tudo. Mas você sabe em que momento usar setInterval() e quando usar setTimeout()? E porquê?

Dá no mesmo, posso usar qualquer uma!

É nesse ponto que fazemos a separação do jovem Padawan para o verdadeiro Ninja: você precisa conhecer o Event Loop do Javascript, e saber como essas funções o afetam.

Como funciona o Javascript

O Javascript trabalha em single thread. Aí você me pergunta: então como ele faz concorrência?

Através do Event Loop!

Cada instrução assíncrona que precisa ser executada em JS, é lançada no Event Loop. Imagine um círculo, com uma única entrada e uma única saída. Todas as instruções entram pelo mesmo lugar, e saem por outro, uma de cada vez.

Como essas instruções são assíncronas, elas ficam rodando ali no Event Loop até que estejam prontas. Com isso, o JS não bloqueia a thread, podendo executar outras instruções. Quando uma instrução finaliza, o JS dispara um evento (daí o nome Event Loop), devolvendo em uma função de callback o resultado dessa instrução.

Instruções síncronas são executadas diretamente, bloqueando a thread.

Vamos ver isso na prática:

1
2
3
4
for( var i = 0; i < 10; i++ ) {
console.log( i );
}
console.log( 'Terminou o for' );

Um loop é uma instrução síncrona, bloqueante. Ou seja, enquanto ele estiver executando, a thread ficará bloqueada. Assim que ele terminar, a próxima instrução é executada. Rodando o exemplo acima no seu console, você verá que, primeiro serão impressos os números de 0 a 9, para então mostrar a mensagem Terminou o for.

E como seria uma instrução assíncrona?

Assim:

1
2
3
4
5
6
7
8
9
10
11
console.log( 'Vou executar antes de setInterval() o/' );
var counter = 0;
var timer = setInterval(function() {
if( counter >= 10 ) {
clearInterval( timer );
}

console.log( counter++ );
}, 1000);

console.log( 'E eu, apesar de estar depois, também vou executar antes do setInterval() xD' );

Porque os console.log() executam antes de setInterval()?

Por que o temporizador é uma função assíncrona. Tanto o setInterval(), quanto o setTimeout(). A função passada como parâmetro não é executada até que o tempo passado no segundo parâmetro se esgote.

O que acontece basicamente é o seguinte: o primeiro console.log() é automaticamente executado. Depois, o setInterval é colocado no Event Loop e, assim que se passar 1 segundo, e ele for o próximo da fila a ser executado (lembra que o Event Loop só executa uma coisa de cada vez?), o JS vai disparar um evento, fazendo com que a função de callback passada no primeiro parâmetro seja executada.

O tempo nunca vai garantir que o que está no callback vai ser executado exatamente naquele tempo, pois podem ter outras funções assíncronas no Event Loop sendo disparadas. Logo que a fila é liberada, e chega a vez do temporizador, aí então ele é executado.

Ficou claro como funciona o Event Loop? Ótimo, vamos seguir!

Sabendo disso, podemos enfim falar sobre as diferenças de setInterval() e setTimeout().

setInterval()

Como o setInterval() executa infinitamente, a garantia que temos é que ele vai adicionar uma instrução ao Event Loop a cada um segundo (levando em consideração os exemplos passados).

Isso significa que, se em 1 segundo a primeira instrução não for executada, - por ter outras instruções no Event Loop - a segunda instrução será adicionada ao Event Loop, ficando duas instruções na fila.

Imagine agora você com vários temporizadores na tela. Cada setInterval() adicionando várias instruções ao Event Loop. Lembrando que o Event Loop é gerenciado pelo browser, no client side. Pode acontecer de chegar um momento que seu computador não terá memória suficiente para suportar a quantidade de eventos alocados, fazer seu navegador dar crash, e em alguns casos, até travar o computador.

Isso já aconteceu com você? Então. O problema, provavelmente, era o setInterval().

setTimeout()

Agora, analisando por esse lado, e sabendo que o setTimeout() só executa uma vez, - a menos que fizermos uma chamada recursiva para que ele execute mais vezes - podemos concluir que, o setTimeout() só vai adicionar mais uma instrução no Event Loop depois que a primeira instrução for retornada através do evento disparado!

Se a primeira instrução demorar 5 segundos para disparar o evento, a segunda instrução só será adicionada DEPOIS disso!

Agora pense no caso do exemplo acima, onde usamos vários setInterval(). Com o setTimeout() não teríamos o mesmo problema, pois cada setTimeout() garante que só uma instrução por setTimeout() será enviada ao Event Loop! É um ganho de performance absurdo! :D

Então, sempre que estiver na dúvida:

setInterval() ou setTimeout() recursivo?

Vá pela segunda opção. É muito mais performática!

Até o próximo artigo!

Sobre o #1postperday: https://blog.da2k.com.br/2014/12/31/um-post-por-dia/

Tem alguma sugestão para os próximos posts do #1postperday? Deixe ela aqui: https://github.com/fdaciuk/fdaciuk.github.io/issues/1