Event Loop
O que é o Event Loop e como ele funciona
O que é o Event Loop?
Um dos principais fatos sobre o JavaScript é que ele é uma linguagem inerentemente single thread, isto é, em qualquer instante de tempo apenas um comando pode estar sendo executado. Essa é uma das características principais da linguagem, independente de onde ela esteja sendo executada. Além disso, o JavaScript é uma linguagem assíncrona, isto é, a ordem de execução das funções não é sempre bem definida. Isso quer dizer que, dependendo de como o código for escrito, cada execução do código pode ter uma ordem diferente de execução de suas funções.
Como o JavaScript é single thread e assíncrono, apenas uma função pode estar sendo executada em qualquer momento, mas a ordem de execução delas deve ser definida durante a execução do código. Assim, o Event Loop (tradução: Ciclo de Eventos) é o mecanismo que o JavaScript usa para escolher qual será a próxima função a ser executada.
O problema
Numa página web, quase sempre que o usuário faz uma ação, a interface deve ser atualizada de alguma forma. Seja para mudar a cor de um link que foi visitado ou para descer o scroll da página. Entretanto, em todos os browsers modernos, a interface não pode ser atualizada enquanto houver código JavaScript sendo executado, e não se pode executar código JavaScript enquanto a interface estiver sendo atualizada (os dois não podem ser feitos ao mesmo tempo).
Assim, se nosso código JavaScript for muito demorado, a tela do usuário ficará travada e irresponsiva, causando uma péssima experiência. Veja um exemplo de código que tem esse comportamento:
No exemplo acima, quando o usuário clicar no botão, toda a interface ficará travada até a função click
terminar, o que pode demorar múltiplos segundos.
A solução
Para mitigar o problema descrito acima, foi-se desenvolvida a ideia de "agendar" a execução de funções num momento futuro, liberando o browser para atualizar a interface do usuário ou executar outras partes do código JavaScript.
No caso do exemplo descrito no problema, a maior parte da "demora" numa requisição HTTP vem da espera pela resposta, e enquanto se espera por essa resposta outra parte do código poderia estar sendo executada (ou a interface do usuário poderia estar sendo atualizada). Outro exemplo parecido é num temporizador, onde uma função só deve ser executada depois de uma quantidade determinada de tempo. Enquanto se espera para executar essa função, o browser poderia ser liberado para terminar outras tarefas.
Assim, algumas funções disponibilizadas pelo browser recebem outras funções como argumento (também conhecidas como Callbacks), que serão executadas apenas quando a operação da primeira função terminar. Veja esse exemplo:
Aqui, quando o usuário clicar no botão, o código vai esperar 5 segundos para mostrar um alerta. Enquanto esse tempo passa, a interface do usuário não ficara travada ou irresponsiva. Isso é porque a função setTimeout
apenas "agenda" a execução da sua callback para quando o temporizador de 5 segundos terminar.
Veja esse outro exemplo, mas dessa vez fazendo uma requisição.
Este é um exemplo um pouco mais complicado, mas que essencialmente faz a mesma coisa que o temporizador. Nesse caso, o código faz uma requisição para um servidor, e agenda a função responseReceived
para ser executada quando uma resposta do servidor for recebida. Essa função, por sua vez, tenta extrair o corpo da resposta na forma de texto, e agenda a função responseTextFinished
para ser executada quando esse texto estiver pronto. Durante todo esse processo, enquanto essas operações estavam sendo feitas, o browser estava livre para atualizar a interface do usuário.
Como funciona
As ferramentas
Para gerenciar as operações e funções agendadas, o browser usa três construtos: a "Call Stack", as "Web APIs" e a "Queue".
A Call Stack (tradução: Pilha de chamadas) é uma pilha de funções que estão sendo executadas no atual instante. Quando uma função do JavaScript é chamada, ela é posta no topo da Call Stack. Quando uma função do JavaScript termina de executar, ela é removida da Call Stack. Se a Call Stack estiver vazia, quer dizer que nenhuma função está sendo executada no momento.
As Web APIs são funções especiais que realizam tarefas complexas por trás dos panos. A maior parte dessas funções são implementadas de forma que utilizam eficientemente os recursos do computador. Essas funções não pertencem ao JavaScript em si, mas são disponibilizadas pelo ambiente de execução (no nosso caso, o browser). Exemplos de Web APIs são: fetch
, setTimeout
ou setInterval
. Você pode ver uma lista das Web APIs mais amplamente implementadas nos browsers modernos aqui.
A Queue (tradução: Fila) é uma fila de callbacks que estão prontas para serem imediatamente executadas, mas que ainda estão aguardando uma oportunidade.
O fluxo
Tudo começa com a primeira execução do código JavaScript. Primeiro, o código que está escrito na página é embrulhado numa grande função e posto no início da Call Stack. Como a Call Stack é a pilha que define qual função está sendo atualmente executada, o código fonte é executado, linha a linha.
Eventualmente, algumas linhas fazem chamadas a algumas Web APIs. Por exemplo, podem-se ter sido feitas chamadas de setTimeout
, ou fetch
. Essas Web APIs sempre recebem uma callback e alguns outros parâmetros. Essas callbacks vão ser "guardadas" até que a Web API termine a sua tarefa. Uma vez que essa tarefa é terminada, a sua callback é imediatamente enviada para o fim da Queue.
Por último vem o trabalho do Event Loop. Pode-se pensar nele como um loop infinito que constantemente verifica se a interface do usuário precisa ser atualizada, se há algo na Queue esperando para ser executado, e se a Call Stack está vazia. A sua lógica pode ser bem descrita de acordo com o seguinte pseudo-código:
Um exemplo simples
Considere o código abaixo:
Nesse exemplo, a primeira coisa que acontece é o código ser imediatamente movido para a Call Stack, e ser imediatamente executado. Assim, a primeira linha imprime a palavra "Início". Na terceira linha, declaramos a função sayHi
. Na oitava linha, fazemos uma chamada para a Web API setTimeout
, que agenda a callback sayHi
para ser executada em 5 segundos. Por fim, na décima linha, imprime-se a string "Fim do código principal".
Ao finalizar a execução desse código, a Call Stack ficará vazia, e o Event Loop começará o seu trabalho de constantemente verificar se há algo na Queue, pronto para mover para a Call Stack. Nesse caso, a única coisa que o nosso código ainda está fazendo é esperar a Web API do setTimeout
terminar a contagem do temporizador de 5 segundos, enquanto a Call Stack e a Queue ficam vazias. Logo, pelos próximo 5 segundos, nada acontece.
Passados os 5 segundos, a Web API do setTimeout
termina o seu trabalho e move a callback sayHi
para a Queue. O Event Loop, que durante todo este tempo estava esperando algo aparecer na Queue, finalmente vê que a Call Stack está vazia e que há algo na Queue. Assim, o Event Loop move a callback sayHi
da Queue para a Call Stack e a executa.
Finalmente, com a função sayHi
na Call Stack e sendo executada, o console.log
da linha 4 é executado, e a string "Oi!" aparece no console. Assim, a impressão final deste código é:
Um exemplo mais complicado
Neste exemplo fazemos 3 requisições HTTP diferentes para o nosso servidor, imprimindo uma string única ao terminar. O processo básico é o mesmo:
Código movido para a Call Stack, com sua execução iniciada.
As três requisições HTTP são iniciadas (através de uma Web API, com suas respectivas callbacks.
A execução do código principal termina, e a Call Stack fica vazia.
As requisições terminam, e suas callbacks são movidas para a Queue.
O Event Loop move as callbacks da Queue para a Call Stack, e inicia a sua execução.
Mas nesse exemplo há algo de mais interessante: são três requisições acontecendo ao mesmo tempo, sem ter ideia de qual vai acabar primeiro. É possível que as duas primeiras levem 3 segundos enquanto a terceira leve 15 segundos. É possível que todas terminem no mesmo exato momento. É possível que uma termine enquanto a callback da outra está sendo executada. São inúmeras possibilidades.
Em casos como esse, quem terminar primeiro poderá empurrar a sua callback para a Queue primeiro. E a ordem de execução das callbacks é a ordem de chegada delas na Queue. Mesmo que as três requisições terminem ao mesmo tempo, alguma delas será tratada primeiro, enquanto a callback da outras espera na Queue a Call Stack se liberar.
Resumo
Neste capítulo, vimos:
Os tipos de problemas que inspiraram a invenção do Event Loop
A solução dos problemas, usando Web APIs.
Como o Event Loop funciona
Alguns exemplos de códigos que usaram Web APIs.
Last updated