Podemos decidir executar uma função não agora, mas em algum momento mais tarde. Isso se chama “agendar uma ligação”.
Existem dois métodos para isso:
setTimeout
nos permite executar uma função uma vez após o intervalo de tempo.
setInterval
nos permite executar uma função repetidamente, começando após um intervalo de tempo e repetindo continuamente nesse intervalo.
Esses métodos não fazem parte da especificação JavaScript. Mas a maioria dos ambientes possui o agendador interno e fornece esses métodos. Em particular, eles são suportados em todos os navegadores e no Node.js.
A sintaxe:
deixe timerId = setTimeout(func|código, [atraso], [arg1], [arg2], ...)
Parâmetros:
func|code
Função ou uma sequência de código a ser executada. Normalmente, isso é uma função. Por razões históricas, uma sequência de código pode ser passada, mas isso não é recomendado.
delay
O atraso antes da execução, em milissegundos (1000 ms = 1 segundo), por padrão 0.
arg1
, arg2
…
Argumentos para a função
Por exemplo, este código chama sayHi()
após um segundo:
function digaOi() { alerta('Olá'); } setTimeout(digaOi, 1000);
Com argumentos:
function digaOi(frase, quem) { alerta(frase + ', ' + quem ); } setTimeout(sayHi, 1000, "Olá", "John"); // Olá, João
Se o primeiro argumento for uma string, o JavaScript criará uma função a partir dela.
Então, isso também funcionará:
setTimeout("alert('Olá')", 1000);
Mas o uso de strings não é recomendado, use funções de seta em vez delas, assim:
setTimeout(() => alerta('Olá'), 1000);
Passe uma função, mas não a execute
Os desenvolvedores novatos às vezes cometem um erro ao adicionar colchetes ()
após a função:
// errado! setTimeout(digaOi(), 1000);
Isso não funciona, porque setTimeout
espera uma referência a uma função. E aqui sayHi()
executa a função, e o resultado de sua execução é passado para setTimeout
. No nosso caso o resultado de sayHi()
é undefined
(a função não retorna nada), então nada é agendado.
Uma chamada para setTimeout
retorna um timerId
de “identificador de timer” que podemos usar para cancelar a execução.
A sintaxe para cancelar:
deixe timerId = setTimeout(...); clearTimeout(timerId);
No código abaixo, agendamos a função e depois cancelamos (mudamos de ideia). Como resultado, nada acontece:
deixe timerId = setTimeout(() => alert("nunca acontece"), 1000); alerta(timerId); // identificador do temporizador clearTimeout(timerId); alerta(timerId); // mesmo identificador (não se torna nulo após o cancelamento)
Como podemos ver na saída alert
, em um navegador o identificador do temporizador é um número. Em outros ambientes, isso pode ser outra coisa. Por exemplo, Node.js retorna um objeto timer com métodos adicionais.
Novamente, não existe uma especificação universal para esses métodos, então tudo bem.
Para navegadores, os temporizadores são descritos na seção de temporizadores do HTML Living Standard.
O método setInterval
tem a mesma sintaxe de setTimeout
:
deixe timerId = setInterval(func|código, [atraso], [arg1], [arg2], ...)
Todos os argumentos têm o mesmo significado. Mas, diferentemente de setTimeout
ele executa a função não apenas uma vez, mas regularmente após um determinado intervalo de tempo.
Para interromper novas chamadas, devemos chamar clearInterval(timerId)
.
O exemplo a seguir mostrará a mensagem a cada 2 segundos. Após 5 segundos, a saída é interrompida:
//repita com intervalo de 2 segundos deixe timerId = setInterval(() => alert('tick'), 2000); // após 5 segundos para setTimeout(() => { clearInterval(timerId); alert('stop'); }, 5000);
O tempo passa enquanto alert
é mostrado
Na maioria dos navegadores, incluindo Chrome e Firefox, o cronômetro interno continua “funcionando” enquanto mostra alert/confirm/prompt
.
Portanto, se você executar o código acima e não descartar a janela alert
por algum tempo, o próximo alert
será mostrado imediatamente quando você fizer isso. O intervalo real entre alertas será inferior a 2 segundos.
Existem duas maneiras de executar algo regularmente.
Um é setInterval
. O outro é um setTimeout
aninhado, assim:
/** em vez de: deixe timerId = setInterval(() => alert('tick'), 2000); */ deixe timerId = setTimeout(função tick() { alerta('marca'); timerId = setTimeout(tick, 2000); // (*) }, 2000);
O setTimeout
acima agenda a próxima chamada logo no final da atual (*)
.
O setTimeout
aninhado é um método mais flexível que setInterval
. Desta forma a próxima chamada poderá ser agendada de forma diferente, dependendo dos resultados da atual.
Por exemplo, precisamos escrever um serviço que envie uma solicitação ao servidor a cada 5 segundos solicitando dados, mas caso o servidor esteja sobrecarregado, deve aumentar o intervalo para 10, 20, 40 segundos…
Aqui está o pseudocódigo:
deixe atraso = 5000; deixe timerId = setTimeout(function request() { ...enviar solicitação... if (solicitação falhou devido à sobrecarga do servidor) { //aumenta o intervalo para a próxima execução atraso *= 2; } timerId = setTimeout(solicitação, atraso); }, atraso);
E se as funções que estamos agendando exigem muita CPU, podemos medir o tempo necessário para a execução e planejar a próxima chamada, mais cedo ou mais tarde.
setTimeout
aninhado permite definir o atraso entre as execuções com mais precisão do que setInterval
.
Vamos comparar dois fragmentos de código. O primeiro usa setInterval
:
seja i = 1; setInterval(função(){ função(i++); }, 100);
O segundo usa setTimeout
aninhado:
seja i = 1; setTimeout(função execução() { função(i++); setTimeout(executar, 100); }, 100);
Para setInterval
o agendador interno executará func(i++)
a cada 100ms:
Você percebeu?
O atraso real entre as chamadas func
para setInterval
é menor do que no código!
Isso é normal, pois o tempo de execução de func
“consome” uma parte do intervalo.
É possível que a execução de func
demore mais do que esperávamos e demore mais de 100 ms.
Nesse caso, o mecanismo aguarda a conclusão func
, depois verifica o escalonador e, se o tempo acabar, executa-o novamente imediatamente .
No caso extremo, se a função sempre for executada por mais tempo do que delay
ms, as chamadas acontecerão sem nenhuma pausa.
E aqui está a imagem do setTimeout
aninhado:
O setTimeout
aninhado garante o atraso fixo (aqui 100ms).
Isso porque está prevista uma nova chamada ao final da anterior.
Coleta de lixo e retorno de chamada setInterval/setTimeout
Quando uma função é passada em setInterval/setTimeout
, uma referência interna é criada para ela e salva no agendador. Impede que a função seja coletada como lixo, mesmo que não haja outras referências a ela.
// a função permanece na memória até que o escalonador a chame setTimeout(função() {...}, 100);
Para setInterval
a função permanece na memória até que clearInterval
seja chamado.
Há um efeito colateral. Uma função faz referência ao ambiente lexical externo, portanto, enquanto ela existe, as variáveis externas também vivem. Eles podem ocupar muito mais memória do que a própria função. Então quando não precisarmos mais da função agendada, é melhor cancelá-la, mesmo que seja muito pequena.
Há um caso de uso especial: setTimeout(func, 0)
ou apenas setTimeout(func)
.
Isso agenda a execução de func
o mais rápido possível. Mas o agendador irá invocá-lo somente após a conclusão do script em execução.
Portanto, a função está programada para ser executada “logo após” o script atual.
Por exemplo, isso gera “Hello” e imediatamente “World”:
setTimeout(() => alert("Mundo")); alerta("Olá");
A primeira linha “coloca a chamada no calendário após 0ms”. Mas o agendador só irá “verificar o calendário” depois que o script atual for concluído, então "Hello"
é o primeiro e "World"
– depois dele.
Existem também casos de uso avançados de tempo limite de atraso zero relacionados ao navegador, que discutiremos no capítulo Loop de eventos: microtarefas e macrotarefas.
O atraso zero na verdade não é zero (em um navegador)
No navegador, há uma limitação de frequência com que os temporizadores aninhados podem ser executados. O HTML Living Standard diz: “após cinco temporizadores aninhados, o intervalo é forçado a ser de pelo menos 4 milissegundos.”.
Vamos demonstrar o que isso significa com o exemplo abaixo. A chamada setTimeout
nele se reprograma com atraso zero. Cada chamada lembra o tempo real da anterior na matriz times
. Como são os atrasos reais? Vejamos:
vamos começar = Date.now(); deixe vezes = []; setTimeout(função execução() { times.push(Date.now() - início); // lembra do atraso da chamada anterior if (início + 100 <Data.agora()) alerta(vezes); //mostra os atrasos após 100ms senão setTimeout(executar); // senão reprogramar }); // um exemplo da saída: //1,1,1,1,9,15,20,24,30,35,40,45,50,55,59,64,70,75,80,85,90,95,100
Os novatos são executados imediatamente (exatamente como está escrito nas especificações) e então vemos 9, 15, 20, 24...
. O atraso obrigatório de 4+ ms entre invocações entra em jogo.
O mesmo acontece se usarmos setInterval
em vez de setTimeout
: setInterval(f)
é f
algumas vezes com atraso zero e depois com atraso de 4+ ms.
Essa limitação vem desde os tempos antigos e muitos scripts dependem dela, por isso existe por razões históricas.
Para JavaScript do lado do servidor, essa limitação não existe e existem outras maneiras de agendar um trabalho assíncrono imediato, como setImmediate para Node.js. Portanto, esta nota é específica do navegador.
Os métodos setTimeout(func, delay, ...args)
e setInterval(func, delay, ...args)
nos permitem executar o func
uma vez/regularmente após delay
milissegundos.
Para cancelar a execução, devemos chamar clearTimeout/clearInterval
com o valor retornado por setTimeout/setInterval
.
As chamadas setTimeout
aninhadas são uma alternativa mais flexível para setInterval
, permitindo-nos definir o tempo entre as execuções com mais precisão.
O agendamento com atraso zero com setTimeout(func, 0)
(o mesmo que setTimeout(func)
) é usado para agendar a chamada “o mais rápido possível, mas após a conclusão do script atual”.
O navegador limita o atraso mínimo para cinco ou mais chamadas aninhadas de setTimeout
ou para setInterval
(após a 5ª chamada) a 4ms. Isso é por razões históricas.
Observe que todos os métodos de agendamento não garantem o atraso exato.
Por exemplo, o cronômetro do navegador pode ficar lento por vários motivos:
A CPU está sobrecarregada.
A guia do navegador está em modo de segundo plano.
O laptop está no modo de economia de bateria.
Tudo isso pode aumentar a resolução mínima do temporizador (o atraso mínimo) para 300 ms ou até 1000 ms, dependendo do navegador e das configurações de desempenho no nível do sistema operacional.
importância: 5
Escreva uma função printNumbers(from, to)
que produza um número a cada segundo, começando com from
e terminando com to
.
Faça duas variantes da solução.
Usando setInterval
.
Usando setTimeout
aninhado.
Usando setInterval
:
function printNumbers(de, para) { deixe atual = de; deixe timerId = setInterval(function() { alerta(atual); if (atual == para) { clearInterval(timerId); } atual++; }, 1000); } // uso: imprimirNúmeros(5, 10);
Usando setTimeout
aninhado:
function printNumbers(de, para) { deixe atual = de; setTimeout(função go() { alerta(atual); if (atual <para) { setTimeout(vai, 1000); } atual++; }, 1000); } // uso: imprimirNúmeros(5, 10);
Observe que em ambas as soluções existe um atraso inicial antes da primeira saída. A função é chamada após 1000ms
na primeira vez.
Se também quisermos que a função seja executada imediatamente, podemos adicionar uma chamada adicional em uma linha separada, como esta:
function printNumbers(de, para) { deixe atual = de; função ir() { alerta(atual); if (atual == para) { clearInterval(timerId); } atual++; } ir(); deixe timerId = setInterval(go, 1000); } imprimirNúmeros(5, 10);
importância: 5
No código abaixo há uma chamada setTimeout
agendada, então é executado um cálculo pesado, que leva mais de 100ms para ser finalizado.
Quando a função agendada será executada?
Depois do ciclo.
Antes do ciclo.
No início do loop.
O que alert
vai mostrar?
seja i = 0; setTimeout(() => alerta(i), 100); // ? // assume que o tempo para executar esta função é >100ms for(seja j = 0; j < 100000000; j++) { eu++; }
Qualquer setTimeout
será executado somente após a conclusão do código atual.
O i
será o último: 100000000
.
seja i = 0; setTimeout(() => alerta(i), 100); //100000000 // assume que o tempo para executar esta função é >100ms for(seja j = 0; j < 100000000; j++) { eu++; }