Olá colegas. Era uma vez 
um artigo sobre Habré, de autoria de John Rezig sobre esse assunto. 10 anos se passaram, e o tópico ainda requer esclarecimentos. Portanto, oferecemos aos interessados a leitura do artigo de Samer Buna, que fornece não apenas uma visão geral teórica dos timers em JavaScript (no contexto do Node.js), mas também tarefas sobre eles.

Algumas semanas atrás, eu twitei a seguinte pergunta em uma única entrevista:
“Onde está o código fonte das funções setTimeout e setInterval? Onde você procuraria por ele? Você não pode pesquisar no Google :) "
*** Responda por si mesmo e continue lendo ***
Cerca de metade das respostas a este tweet estavam incorretas. Não, o caso NÃO está relacionado à V8 (ou outras VMs) !!! Funções como 
setTimeout e 
setInterval , orgulhosamente chamadas de "JavaScript Timers", não fazem parte de nenhuma especificação ECMAScript ou implementação de mecanismo JavaScript. As funções de timer são implementadas no nível do navegador, portanto, sua implementação difere em diferentes navegadores. Os cronômetros também são implementados nativamente no próprio tempo de execução do Node.js.
Nos navegadores, as principais funções do timer referem-se à interface do 
Window , que também está associada a outras funções e objetos. Essa interface fornece acesso global a todos os seus elementos no escopo principal do JavaScript. É por isso que a função 
setTimeout pode ser executada diretamente no console do navegador.
No Nó, os temporizadores fazem parte do objeto 
global , projetado como a interface do navegador do 
Window . O código fonte dos temporizadores no Nó é mostrado 
aqui .
Pode parecer para alguém que essa é apenas uma pergunta ruim da entrevista - de que serve saber isso ?! Eu, como desenvolvedor JavaScript, penso assim: pressupõe-se que você deva saber disso, pois o oposto pode indicar que você não entende bem como a V8 (e outras máquinas virtuais) interage com navegadores e Nó.
Vamos dar uma olhada em alguns exemplos e resolver algumas tarefas do timer, vamos?
Você pode usar o comando node para executar os exemplos neste artigo. A maioria dos exemplos discutidos aqui é apresentada no meu curso Introdução ao Node.js no Pluralsight.Execução de função adiadaTimers são funções de ordem superior com as quais você pode atrasar ou repetir a execução de outras funções (o timer recebe uma função como o primeiro argumento).
Aqui está um exemplo de execução adiada:
 
Neste exemplo, usando 
setTimeout a mensagem de saudação é adiada por 4 segundos. O segundo argumento para 
setTimeout é o atraso (em ms). Multiplico 4 por 1000 para obter 4 segundos.
O primeiro argumento para 
setTimeout é uma função cuja execução será atrasada.
Se você executar o arquivo 
example1.js com o comando node, o Node fará uma pausa por 4 segundos e exibirá uma mensagem de boas-vindas (seguida por uma saída).
Observe: o primeiro argumento para 
setTimeout é apenas uma 
referência de função . Não deve ser uma função 
example1.js - como 
example1.js . Aqui está o mesmo exemplo sem usar a função interna:
 const func = () => { console.log('Hello after 4 seconds'); }; setTimeout(func, 4 * 1000); 
Passando argumentosSe a função para a qual 
setTimeout é usado para atrasar aceitar algum argumento, você poderá usar os argumentos restantes da própria função 
setTimeout (depois daqueles 2 que já aprendemos) para transferir os valores dos argumentos para a função adiada.
 
Aqui está um exemplo:
 
A função de 
rocks acima, atrasada por 2 segundos, pega o argumento 
who e a chamada 
setTimeout passa a ele o valor "Node.js" como um argumento 
who .
Ao executar o 
example2.js com o comando 
node , a frase “Node.js rocks” será exibida após 2 segundos.
Tarefa de Timers # 1Portanto, com base no material já estudado sobre o 
setTimeout , exibiremos as 2 mensagens a seguir após os atrasos correspondentes.
- A mensagem "Olá após 4 segundos" é exibida após 4 segundos.
- A mensagem "Olá após 8 segundos" é exibida após 8 segundos.
LimitaçãoNa sua solução, você pode definir apenas uma função que contenha funções internas. Isso significa que muitas chamadas 
setTimeout terão que usar a mesma função.
SoluçãoAqui está como eu resolveria esse problema:
 
Para mim, 
theOneFunc recebe o argumento de 
delay e usa o valor desse argumento de 
delay na mensagem exibida na tela. Assim, a função pode exibir mensagens diferentes, dependendo do valor do atraso que iremos informar.
Em seguida, usei o 
theOneFunc em duas chamadas 
setTimeout , com a primeira sendo acionada após 4 segundos e a segunda após 8 segundos. Essas duas chamadas 
setTimeout também recebem um terceiro argumento, representando o argumento de 
delay para o 
theOneFunc .
Após a execução do arquivo 
solution1.js com o comando node, exibiremos os requisitos da tarefa e a primeira mensagem aparecerá após 4 segundos e a segunda após 8 segundos.
Repita a funçãoMas e se eu pedisse para você exibir uma mensagem a cada 4 segundos, por um tempo ilimitado?
Obviamente, você pode 
setTimeout em um loop, mas a API do timer também oferece a função 
setInterval , com a qual você pode programar a execução "eterna" de qualquer operação.
Aqui está um exemplo de 
setInterval :
 
Este código exibirá uma mensagem a cada 3 segundos. Se você executar o 
example3.js com o comando 
node , o Node emitirá esse comando até você forçar o término do processo (CTRL + C).
Cancelar temporizadoresComo uma ação é atribuída quando a função de timer é chamada, essa ação também pode ser desfeita antes de ser executada.
A chamada 
setTimeout retorna um ID do timer e você pode usá-lo ao chamar 
clearTimeout para cancelar o timer. Aqui está um exemplo:
 
Esse timer simples deve disparar após 0 ms (ou seja, imediatamente), mas isso não acontecerá, pois capturamos o valor de 
timerId e cancelamos imediatamente esse timer chamando 
clearTimeout .
Ao executar o 
example4.js com o comando 
node , o Node não imprime nada - o processo simplesmente termina imediatamente.
A propósito, o Node.js também fornece outra maneira de 
setTimeout com um valor de 0 ms. Há outra função na API do timer do 
setImmediate chamada 
setImmediate , e basicamente faz a mesma coisa que 
setTimeout com um valor de 0 ms, mas nesse caso você pode omitir o atraso:
 setImmediate( () => console.log('I am equivalent to setTimeout with 0 ms'), ); 
A função 
setImmediate é 
suportada em todos os navegadores . Não o use no código do cliente.
Juntamente com 
clearTimeout há uma função 
clearInterval que faz o mesmo, mas com chamadas 
setInerval , e também há uma chamada 
clearImmediate .
Atraso no temporizador - algo que não é garantidoVocê notou que no exemplo anterior, ao executar uma operação com 
setTimeout após 0 ms, essa operação não ocorre imediatamente (após 
setTimeout ), mas somente após todo o código de script ter sido completamente executado (incluindo a chamada 
clearTimeout )?
Deixe-me esclarecer este ponto com um exemplo. Aqui está uma chamada 
setTimeout simples que deve funcionar em meio segundo - mas isso não acontece:
 
Imediatamente após definir o cronômetro neste exemplo, bloqueamos de forma síncrona o ambiente de tempo de execução com um loop 
for grande. O valor de 
1e10 é 1 com 10 zeros, portanto, o ciclo dura 10 bilhões de ciclos de processador (em princípio, isso simula um processador sobrecarregado). O nó não pode fazer nada até que esse loop seja concluído.
Obviamente, na prática, isso é muito ruim, mas este exemplo ajuda a entender que o atraso 
setTimeout não é garantido, mas o 
valor mínimo . Um valor de 500 ms significa que o atraso durará pelo menos 500 ms. De fato, o script levará muito mais tempo para exibir a linha de boas-vindas na tela. Primeiro, ele terá que esperar até que o ciclo de bloqueio seja concluído.
Problema # 2 dos temporizadoresEscreva um script que exibirá a mensagem "Hello World" uma vez por segundo, mas apenas 5 vezes. Após 5 iterações, o script deve exibir uma mensagem "Concluído", após o qual o processo do Nó será concluído.
Limitação : ao resolver esse problema, você não pode chamar 
setTimeout .
Dica : precisa de um contador.
SoluçãoAqui está como eu resolveria esse problema:
 let counter = 0; const intervalId = setInterval(() => { console.log('Hello World'); counter += 1; if (counter === 5) { console.log('Done'); clearInterval(intervalId); } }, 1000); 
Defino 0 como o valor inicial do 
counter e, em seguida, chamo 
setInterval , que leva seu ID.
Uma função adiada exibirá uma mensagem e cada vez aumentará o contador em um. Dentro da função adiada, temos uma instrução if, que verificará se 5 iterações já foram aprovadas. Após 5 iterações, o programa exibe "Concluído" e limpa o valor do intervalo usando a constante 
intervalId capturada. O atraso do intervalo é de 1000 ms.
Quem chama exatamente funções adiadas?Ao usar o JavaScript, 
this dentro de uma função regular, como esta, por exemplo:
 function whoCalledMe() { console.log('Caller is', this); } 
o valor na 
this corresponderá ao 
chamador . Se você definir a função acima dentro do Nó REPL, o objeto 
global chamará. Se você definir uma função no console do navegador, o objeto da 
window chamará.
Vamos definir uma função como propriedade de um objeto para torná-lo um pouco mais claro:
 const obj = { id: '42', whoCalledMe() { console.log('Caller is', this); } };  
Agora, quando usaremos o link diretamente ao trabalhar com a função 
obj.whoCallMe , o objeto 
obj (identificado por seu 
id ) atuará como o chamador:

Agora a pergunta é: quem será o chamador se você passar o link para 
obj.whoCallMe para 
setTimetout ?
 
Quem é o responsável pela chamada neste caso?A resposta será diferente dependendo de onde a função de timer é executada. Nesse caso, a dependência de quem é o chamador é simplesmente inaceitável. Você perderá o controle sobre o chamador, pois dependerá da implementação do timer que, neste caso, chama sua função. Se você testar esse código em um nó REPL, o objeto 
Timeout será o chamador:

Observe: isso é importante apenas quando o JavaScript 
this usado dentro de funções regulares. Ao usar as funções de seta, o chamador não deve incomodá-lo.
Problema # 3 dos temporizadoresEscreva um script que produza continuamente uma mensagem "Hello World" com atrasos variados. Comece com um atraso de um segundo e aumente-o em um segundo a cada iteração. Na segunda iteração, o atraso será de 2 segundos. No terceiro - três, e assim por diante.
Inclua um atraso na mensagem exibida. Você deve obter algo como isto:
Hello World. 1
Hello World. 2
Hello World. 3
...Limitações : as variáveis podem ser definidas apenas usando const. Usar let ou var não é.
SoluçãoComo a duração do atraso nesta tarefa é uma variável, não é possível usar 
setInterval aqui, mas você pode configurar manualmente a execução do intervalo usando 
setTimeout dentro de uma chamada recursiva. A primeira função executada com 
setTimeout criará o próximo timer, e assim por diante.
Além disso, como você não pode usar 
let / 
var , não podemos ter um contador para aumentar o atraso de cada chamada recursiva; em vez disso, você pode usar os argumentos de uma função recursiva para executar um incremento durante uma chamada recursiva.
Veja como resolver esse problema:
 const greeting = delay => setTimeout(() => { console.log('Hello World. ' + delay); greeting(delay + 1); }, delay * 1000); greeting(1); 
Tarefa de temporizadores # 4Escreva um script que exibirá a mensagem “Hello World” com a mesma estrutura de atraso da tarefa nº 3, mas desta vez em grupos de 5 mensagens, e o grupo terá um intervalo de atraso principal. Para o primeiro grupo de 5 mensagens, selecionamos o atraso inicial de 100 ms, para o próximo - 200 ms, para o terceiro - 300 ms e assim por diante.
Aqui está como esse script deve funcionar:
- Com 100 ms, o script exibe "Hello World" pela primeira vez e o faz 5 vezes com um intervalo aumentando em 100 ms. A primeira mensagem aparecerá após 100 ms, a segunda após 200 ms, etc.
- Após as 5 primeiras mensagens, o script deve aumentar o atraso principal em 200 ms. Assim, a 6ª mensagem será exibida após 500 ms + 200 ms (700 ms), 7 a 900 ms, 8ª mensagem - após 1100 ms e assim por diante.
- Após 10 mensagens, o script deve aumentar o intervalo de atraso principal em 300 ms. A 11ª mensagem deve ser exibida após 500 ms + 1000 ms + 300 ms (18000 ms). A 12ª mensagem deve ser exibida após 2100 ms, etc.
De acordo com esse princípio, o programa deve funcionar indefinidamente.
Inclua um atraso na mensagem exibida. Você deve obter algo parecido com isto (sem comentários):
Hello World. 100 // 100
Hello World. 100 // 200
Hello World. 100 // 300
Hello World. 100 // 400
Hello World. 100 // 500
Hello World. 200 // 700
Hello World. 200 // 900
Hello World. 200 // 1100
...Limitações : você pode usar apenas chamadas para 
setInterval (e não 
setTimeout ) e apenas ONE 
if .
SoluçãoComo só podemos trabalhar com chamadas 
setInterval , aqui precisamos usar recursão e também aumentar o atraso da próxima chamada 
setInterval . Além disso, precisamos da 
if para fazer isso acontecer somente após 5 chamadas para essa função recursiva.
Aqui está uma solução possível:
 let lastIntervalId, counter = 5; const greeting = delay => { if (counter === 5) { clearInterval(lastIntervalId); lastIntervalId = setInterval(() => { console.log('Hello World. ', delay); greeting(delay + 100); }, delay); counter = 0; } counter += 1; }; greeting(100); 
Obrigado a todos que leram.