A Magia do Event Loop
Tags: javascript, node, programming
Published: 2023-08-02 00:32:31

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

```javascript
setTimeout(() => console.log(1), 10)
Promise.resolve().then(() => console.log(2))
console.log(3)
```

Se a sua resposta foi diferente de:
```javascript
3
2
1
```

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**](#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)](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b8ry8ueju3rhnvuvnwnq.png)

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`](https://libuv.org/), 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mrcidhlqrcjp8xk4swtc.png)

Após iniciar o _Timer_, a _thread_ principal removerá a instrução da _Call Stack_.

![Main Thread pops from call stack](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/itlk0d6813hhs7d4uyum.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2yphjc4rv34lmr0y3h84.png)

A instrução da vez é a
```javascript
Promise.resolve().then(() => console.log(2))
```

A _thread_ principal vai executar a função [Promise](https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/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()](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/usel8ojsgldntndmdw1z.png)

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()](https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Promise/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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5tx6tt1l0mt6yd31vdqe.png)

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


![Estado atual da execução do script](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l1l4p3kq8lcvzxshxwgk.png)


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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dff0sbaxt7gnbqe2i0ch.png)



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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e7gq0vg8ceov0ni2zmj8.png)

Essa é uma instrução simples, que exibe um valor no _console_, seu resultado é:
```bash
3
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7lyjf1z0m4ohlbsg6ou7.png)

Em seguida, a _Main Thread_ consome a instrução da _Call Stack_ e a executa.
![Main Thread consumindo call stack](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zs02h5a9oejxr7ub701c.png)

```javascript
console.log(2) // Escreve 2 no console
```
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ztyrlifqa2zdf8izsqg1.png)


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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xm31nxba5ktgh0ia781k.png)

Então, a _Main Thread_ consome a última instrução da _Call Stack_ e a executa.
![Main Thread consumindo a Call Stack](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y7sqoyvdrk692lybb9im.png)

```javascript
console.log(1) // Escreve 1 no console
```

**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:
```javascript
setTimeout(() => console.log(1), 10)
Promise.resolve().then(() => console.log(2))
console.log(3)
```

Resultará em:

```javascript
3
2
1
```

> 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](https://tc39.es/ecma262/2023/) 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](https://hcode.com.br/blog/o-que-e-ecmascript-e-o-mesmo-que-javascript)

### 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](https://nodejs.org/), [V8](https://v8.dev/) e [SpiderMonkey](https://firefox-source-docs.mozilla.org/js/index.html) são os _runtimes_ JavaScript mais conhecidos do mundo.