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.