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 | setTimeout( function() { |
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ê executeclearInterval()
;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 | var counter = 0; |
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 | var counter = 0; |
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 | for( var i = 0; i < 10; i++ ) { |
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 | console.log( 'Vou executar antes de setInterval() o/' ); |
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