DEV Community

Cover image for A Magia do Event Loop
Caio Borghi
Caio Borghi

Posted on • Edited on

A Magia do Event Loop

O que acontece quando o seguinte código é executado no Node.js?

setTimeout(() => console.log(1), 10)
Promise.resolve().then(() => console.log(2))
console.log(3)
Enter fullscreen mode Exit fullscreen mode

Se a sua resposta foi diferente de:

3
2
1
Enter fullscreen mode Exit fullscreen mode

Talvez você não entenda muito bem a ordem de execução do JavaScript e o funcionamento do Event Loop.

Sem problemas, vou tentar explicar.

Antes de tudo, se você tem dúvidas sobre o que é:

  • JavaScript
  • ECMAScript
  • Runtime de JavaScript

Eu recomendo que você leia o glossário antes de continuar.

Agora, vamos lá, vou explicar o que acontece em cada etapa da execução desse código JavaScript.

Main Thread

O Node interpreta o arquivo JavaScript de cima para baixo, linha por linha, em uma única thread.

Executando o setTimeout()

A main thread interpretará a primeira instrução, adicionará na Call Stack, onde será executada e removida da Call Stack.

Visualização da Main Thread executando a primeira chamada de função: setTimeout(() => console.log(1), 10)

A instrução setTimeout serve para agendar a execução de uma função após determinados millisegundos.

Essa função faz parte da biblioteca libuv, que o Node utiliza para criar um Timer sem bloquear a thread principal.

A main thread executa a função setTimeout, que inicia um cronômetro em uma nova thread, através de uma biblioteca chamada libuv. Ao final do cronometro, o callback será adicionado à fila de macro-tarefas

Após iniciar o Timer, a thread principal removerá a instrução da Call Stack.

Main Thread pops from call stack

Ao final do intervalo, o timer vai adicionar o callback da função setTimeout na fila de macro-tarefas.

Executando Promise.resolve().then()

Enquanto o Timer da biblioteca libuv espera os 10ms, a Main thread interpretará a próxima linha do arquivo.

Main thread consome a próxima instrução da call stack

A instrução da vez é a

Promise.resolve().then(() => console.log(2))
Enter fullscreen mode Exit fullscreen mode

A thread principal vai executar a função Promise.resolve().then()

Promise é um objeto que representa uma conclusão ou falha de uma operação assíncrona.

Ao chamar a função resolve() sem nenhum parâmetro, estamos declarando Promise que não retorna valor algum, mas tudo bem.

Executando a função Promise.resolve()

Por ora, estamos mais interessados no comportamento da função .then de uma Promise.

Ao passar () => console.log(2) como calback para nossa Promise, estamos dizendo para o Node executar este código assim que a Promise for finalizada com sucesso.

Ou seja, estamos dizendo que, assim que o método resolve() da Promise for executado, o Node deverá executar nossa instrução console.log(2).

Mas, não é bem assim que funciona.

Todo callback de Promise é enviado instantaneamente para uma fila especial chamada Micro Tasks Queue.

Pushes Promise callback to MicroTasks queue

Recapitulando

Esse é o estado atual da execução do script:

Estado atual da execução do script

Tudo que aconteceu até agora, com certeza, levou menos de 10 millissegundos, por isso que o Timer ainda não adicionou a instrução de console.log(1) na Macro Tasks Queue.

Mas, por utilizar a libuv, a Main thread pode continuar trabalhando normalmente, de maneira não-bloqueante.

Ok, você pode estar se perguntando:

Event Loop

Durante todo esse processo, a cada interpretação de nova linha do arquivo, o Event Loop realizou uma função muito importante, embora repetitiva.

  • Verificar se a Call Stack estava vazia.

Event Loop perguntando se a Call Stack está vazia

Como você pode observar, a resposta foi sempre: NÃO!

Em nenhum momento durante a execução desse script a Call Stack ficou vazia, então, nosso amigo Event Loop continuará esperando.

Esvaziando a Call Stack

Agora, a Main Thread interpreta a última instrução do arquivo.

Main Thread consome a última chamada da pilha de chamadas

Essa é uma instrução simples, que exibe um valor no console, seu resultado é:

3
Enter fullscreen mode Exit fullscreen mode

E, pela primeira vez, a Call Stack fica vazia!

Event Loop

Agora sim, o momento mais esperado pelo Event Loop, o momento que ele tem o poder de agir!

Ele só vai validar as outras filas quando a Call Stack estiver vazia!

A cada loop, ele vai:

  • Processar todas as tarefas da fila de Micro Tasks
    • Adicionando-as à Call Stack
  • Processar 1 tarefa da fila de Macro Tasks
    • Adicionando-a à Call Stack
  • Esperar a Call Stack esvaziar
  • Repetir

A Main Thread executa toda instrução no contexto principal.

Agora, continuando a execução do código de exemplo:

Micro Tasks

Quando a Call Stack fica vazia, significa que a Main Thread não está executando nada.

Então, o Event Loop consome todas as tarefa da Micro Tasks Queue e adiciona à Call Stack
Event Loop consumindo a função da fila de micro tarefas

Em seguida, a Main Thread consome a instrução da Call Stack e a executa.
Main Thread consumindo call stack

console.log(2) // Escreve 2 no console
Enter fullscreen mode Exit fullscreen mode

Agora, a Call Stack volta a ficar vazia.

Então, o Event Loop busca por mais tarefas na fila de Micro Tasks.

Estado atual da aplicação

Como está vazia, ele finaliza o seu trabalho na Micro Tasks Queue e vai começar a consumir a Macro Tasks Queue.

Macro Tasks

Agora, supondo que o intervalo de 10 millisegundos já tenha passado e o Timer tenha inserido a função de console.log(1) na fila de Macro Tasks, o Event Loop transferirá 1 instrução da Macro Tasks Queue para a Call Stack.

Event Loop consumindo fila de Macro Tasks

Então, a Main Thread consome a última instrução da Call Stack e a executa.
Main Thread consumindo a Call Stack

console.log(1) // Escreve 1 no console
Enter fullscreen mode Exit fullscreen mode

Ponto importante: Se ainda houvesse instruções na fila de Micro Tasks, estas seriam processadas. Mas, como tudo está vazio, a execução do programa caminha para o fim.

É por isso que o código:

setTimeout(() => console.log(1), 10)
Promise.resolve().then(() => console.log(2))
console.log(3)
Enter fullscreen mode Exit fullscreen mode

Resultará em:

3
2
1
Enter fullscreen mode Exit fullscreen mode

Chegamos ao fim - Arlindo Cruz

Agora você entende o que acontece nos bastidores do JavaScript. O Event Loop gerencia as filas de micro e macro tarefas e, com isso, garante que instruções assíncronas sejam executadas com harmonia no contexto da thread principal.

Entender seu funcionamento nos ajuda a escrever códigos mais eficientes e a prever melhor o comportamento de nossas aplicações.

Da próxima vez que for escrever código em JavaScript, espero que se lembre de tudo que acontece nos bastidores do Event Loop.

Até mais!

Glossário

JavaScript

É uma linguagem de programação de alto nível, dinâmica, interpretada, que suporta múltiplos paradigmas de programação (funcional, imperativo, orientado a objetos).

É um "meio" de conversa entre algo que você quer fazer e que o computador executa.

ECMAScript

É um conjunto de regras que define como o JavaScript deve funcionar, ela define os padrões da linguagem (sintaxe, tipos de dados, estruturas de controle e operadores), e o JavaScript é a implementação desses padrões.

Se quiser entender melhor, leia esse artigo

Runtime JavaScript

É o motor que executa o código JavaScript.

Ao escrever código JavaScript, você escreve instruções (que seguem as regras definidas pelo ECMAScript), mas para executar essas instruções, você precisa de um Runtime.

É como se o JavaScript fosse uma receita e o Runtime fosse um cozinheiro que executa a receita.

Node, V8 e SpiderMonkey são os runtimes JavaScript mais conhecidos do mundo.

Top comments (7)

Collapse
 
valdineifer profile image
Valdinei Ferreira

Achei o post ótimo. Coincidentemente, eu estava estudando (aos poucos) essa parte do JavaScript enquanto você comentava no Twitter.
Só acho que faltou uma definição clara do que entraria em micro/macro tasks queue.

Collapse
 
ocodista profile image
Caio Borghi

Valeu!!!

MicroTasks: Callback de Promises / Callbacks adicionados via queueMicrotask / process.nextTick

MacroTasks: Callback de timers (setTimeout, setInterval), eventos de dom (onclick, onmouseover)

Collapse
 
guridocodigo profile image
Guri do Código • Edited

Conteúdo Rico, vlw D+

Mas gostaria muito de ver tudo isso de forma visual

Collapse
 
matheusbrasil profile image
Matheus Brasil

Muito obrigado pelo conteúdo. Já está aqui para estudo.

Collapse
 
tmm profile image
ThiagoMassenoMaciel

Ótimo biblioteca seu perfil . Principalmente para eu que estou iniciando

Collapse
 
ocodista profile image
Caio Borghi

Muito obrigado, Thiago!

Boa sorte nos estudos!

Collapse
 
jpburgarelli profile image
joão burgarelli

Explicação sensacional e direta!