Embora o desenvolvimento de código de produção em grande escala possa requerer um conhecimento aprofundado de linguagens como C++ e C, a programação em JavaScript pode ser iniciada com uma compreensão básica de suas capacidades.
Conceitos como a passagem de callbacks para funções ou a escrita de código assíncrono, geralmente não apresentam grandes desafios de implementação, o que leva muitos desenvolvedores JavaScript a se preocuparem menos com o funcionamento interno da linguagem. Eles simplesmente não se aprofundam nas complexidades que são abstraídas pela linguagem.
No entanto, como desenvolvedor JavaScript, torna-se cada vez mais importante entender os bastidores e como essas complexidades funcionam. Isso nos capacita a tomar decisões mais informadas, que podem, por sua vez, melhorar significativamente o desempenho do nosso código.
Este artigo aborda um conceito fundamental, embora muitas vezes mal compreendido, em JavaScript: o Event Loop!
A escrita de código assíncrono é inevitável em JavaScript, mas qual é o significado exato de executar código de forma assíncrona? A resposta está no Event Loop.
Antes de entendermos o funcionamento do Event Loop, vamos primeiro compreender o que é o JavaScript e como ele opera!
O Que É JavaScript?
Antes de avançarmos, vamos revisar o básico. O que é, de fato, JavaScript? Poderíamos definir JavaScript como:
JavaScript é uma linguagem de programação de alto nível, interpretada, simultânea, assíncrona, de thread único e não bloqueante.
Essa é uma definição formal, não é? 🤔 Vamos desmembrá-la!
As palavras-chave relevantes para este artigo são: single-threaded, non-blocking, concorrente e assíncrono.
Thread Único
Um thread de execução é a menor sequência de instruções programadas que pode ser gerenciada independentemente por um escalonador. Uma linguagem de programação é considerada de thread único quando ela só pode executar uma tarefa ou operação por vez. Isso significa que um processo inteiro é executado do início ao fim sem que o thread seja interrompido ou pausado.
Diferentemente de linguagens multithread, onde vários processos podem ser executados simultaneamente em vários threads sem bloquear uns aos outros.
Como JavaScript pode ser de thread único e não bloqueante ao mesmo tempo?
Mas o que significa “bloquear”?
Não Bloqueante
Não há uma definição formal de bloqueio. Significa simplesmente operações que são lentas no thread. Portanto, não bloqueante significa operações que são rápidas no thread.
Mas espere! Eu mencionei que JavaScript é executado em um único thread? E também disse que ele é não bloqueante, o que implica que as tarefas são executadas rapidamente na pilha de chamadas? Como isso acontece? E os temporizadores? As rotações?
Calma! Vamos entender isso em breve 😉.
Simultâneo
Simultaneidade significa que o código está sendo executado ao mesmo tempo por mais de um thread.
As coisas estão ficando complexas agora. Como JavaScript pode ser de thread único e simultâneo ao mesmo tempo? Ou seja, executar seu código com mais de um thread?
Assíncrono
A programação assíncrona implica que o código é executado em um Event Loop. Quando ocorre uma operação de bloqueio, o evento é iniciado. O código de bloqueio continua a ser executado sem bloquear o thread de execução principal. Assim que o código de bloqueio termina a execução, ele enfileira o resultado das operações de bloqueio e as envia de volta para a pilha.
Mas JavaScript tem apenas um único thread. O que então executa esse código de bloqueio permitindo que outros códigos no thread continuem a ser executados?
Antes de prosseguirmos, vamos recapitular o que discutimos até agora.
- JavaScript é de thread único
- JavaScript é não bloqueante, ou seja, processos lentos não impedem sua execução
- JavaScript é simultâneo, ou seja, executa seu código em mais de um thread ao mesmo tempo
- JavaScript é assíncrono, ou seja, executa código de bloqueio em outro lugar.
O que foi dito acima parece contraditório. Como uma linguagem de thread único pode ser não bloqueante, simultânea e assíncrona?
Vamos aprofundar um pouco mais e explorar o mecanismo de tempo de execução JavaScript, V8. Talvez ele tenha alguns threads ocultos que desconhecemos.
Motor V8
O mecanismo V8 é um mecanismo de tempo de execução WebAssembly de código aberto e de alto desempenho para JavaScript, desenvolvido em C++ pelo Google. A maioria dos navegadores utiliza o mecanismo V8 para executar JavaScript, e o popular ambiente de tempo de execução Node.js também o emprega.
Simplificando, V8 é um programa C++ que recebe o código JavaScript, o compila e o executa.
O V8 realiza duas funções principais:
- Alocação de memória heap
- Contexto de execução da pilha de chamadas
Infelizmente, nossa suspeita estava errada. O V8 possui apenas uma pilha de chamadas. Podemos considerar a pilha de chamadas como o thread.
Um thread === uma pilha de chamadas === uma execução por vez.
Como o V8 possui apenas uma pilha de chamadas, como JavaScript consegue executar simultaneamente e de forma assíncrona sem bloquear o thread de execução principal?
Vamos tentar descobrir escrevendo um código assíncrono simples e comum e analisá-lo juntos.
O JavaScript executa cada linha de código sequencialmente, uma após a outra (single-threaded). Como esperado, a primeira linha é impressa no console. Mas por que a última linha é impressa antes do código de timeout? Por que o processo de execução não aguarda o código de timeout (bloqueante) antes de prosseguir para a execução da última linha?
Parece que algum outro thread nos auxiliou na execução desse timeout, pois sabemos que um thread só pode executar uma única tarefa por vez.
Vamos dar uma olhada no código fonte do V8 por um instante.
Espere! Não há funções de timer no V8, nem DOM? Sem eventos? Sem AJAX?…. Eita!!!
Eventos, DOM, temporizadores, etc., não fazem parte da implementação principal do JavaScript. O JavaScript segue estritamente as especificações Ecma Scripts, e as várias versões são frequentemente referidas de acordo com suas especificações Ecma Scripts (ES X).
Fluxo de Trabalho de Execução
Eventos, temporizadores e solicitações Ajax são fornecidos do lado do cliente pelos navegadores, e são geralmente chamados de APIs da Web. São eles que permitem que o JavaScript de thread único seja não bloqueante, simultâneo e assíncrono! Mas como?
Existem três seções principais no fluxo de trabalho de execução de qualquer programa JavaScript: a pilha de chamadas, a API da Web e a fila de tarefas.
A Pilha de Chamadas
Uma pilha é uma estrutura de dados onde o último elemento adicionado é sempre o primeiro a ser removido da pilha. Podemos pensar nisso como uma pilha de pratos, onde apenas o primeiro prato que foi adicionado por último pode ser removido primeiro. Uma pilha de chamadas é simplesmente uma estrutura de dados de pilha onde as tarefas ou o código são executados de acordo com essa ordem.
Vamos considerar o exemplo abaixo:
Fonte – https://youtu.be/8aGhZQkoFbQ
Quando você chama a função `printSquare()`, ela é inserida na pilha de chamadas. A função `printSquare()` chama a função `square()`. A função `square()` é inserida na pilha e também chama a função `multiple()`. A função `multiple()` é inserida na pilha. Como a função `multiple()` retorna e é a última coisa que foi colocada na pilha, ela é resolvida primeiro e removida da pilha, seguida pela função `square()` e, finalmente, pela função `printSquare()`.
A API da Web
É aqui que o código que não é tratado pelo mecanismo V8 é executado para evitar o “bloqueio” do thread de execução principal. Quando a pilha de chamadas encontra uma função da API da Web, o processo é imediatamente entregue à API da Web, onde está sendo executado, liberando a pilha de chamadas para realizar outras operações durante sua execução.
Voltemos ao nosso exemplo de `setTimeout` acima;
Quando executamos o código, a primeira linha, `console.log`, é enviada para a pilha e obtemos nossa saída quase imediatamente. Ao atingir o `setTimeout`, os temporizadores são manipulados pelo navegador e não fazem parte da implementação principal do V8. Eles são, em vez disso, enviados para a API da Web, liberando a pilha para que ela possa executar outras operações.
Enquanto o timeout ainda está em execução, a pilha passa para a próxima linha de ação e executa o último `console.log`, o que explica por que obtemos a saída antes da saída do temporizador. Assim que o temporizador é concluído, algo acontece. O `console.log` do temporizador aparece magicamente na pilha de chamadas novamente!
Como?
O Event Loop
Antes de discutirmos o Event Loop, vamos primeiro examinar a função da fila de tarefas.
Voltando ao nosso exemplo de timeout, uma vez que a API da Web conclui a execução da tarefa, ela não a envia automaticamente de volta para a pilha de chamadas. Ela vai para a fila de tarefas.
Uma fila é uma estrutura de dados que funciona de acordo com o princípio “Primeiro a Entrar, Primeiro a Sair”, de modo que as tarefas são colocadas na fila e saem na mesma ordem. As tarefas que foram executadas pelas APIs da Web, e que estão sendo enviadas para a fila de tarefas, voltam para a pilha de chamadas para ter o resultado impresso.
Mas espere. O QUE DIABOS É O EVENT LOOP???
Fonte – https://youtu.be/8aGhZQkoFbQ
O Event Loop é um processo que aguarda a limpeza da pilha de chamadas antes de enviar retornos de chamada da fila de tarefas para a pilha de chamadas. Depois que a pilha é limpa, o Event Loop é acionado e verifica a fila de tarefas em busca de retornos de chamada disponíveis. Se houver algum, ele o envia para a pilha de chamadas, aguarda a limpeza da pilha de chamadas novamente e repete o mesmo processo.
Fonte – https://www.quora.com/How-does-an-event-loop-work/answer/Timothy-Maxwell
O diagrama acima demonstra o fluxo de trabalho básico entre o Event Loop e a fila de tarefas.
Conclusão
Embora esta seja uma introdução básica, o conceito de programação assíncrona em JavaScript fornece informações suficientes para que você compreenda claramente o que acontece nos bastidores e como o JavaScript pode ser executado de forma simultânea e assíncrona com apenas um único thread.
JavaScript está sempre em demanda, e se você está curioso para aprender mais, sugiro que dê uma olhada neste curso da Udemy.