JavaScript - Event Loop

nascimento_

Jorge Nascimento

Posted on March 1, 2023

JavaScript - Event Loop

JavaScript - Event Loop

O event loop é o mecanismo que permite a execução de código assíncrono no Javascript, sendo I/O não bloqueante. Sabemos que o JS é single-threaded, ou seja, executa uma coisa por vez, e precisa lidar com chamadas que demandam tempo em background para deixar a sua Call Stack livre para continuar executando o código.

Vamos caminhar pelos conceitos do event loop e entender o que acontece por deixa dos panos.

A engine Javascript

Uma engine Javascript é um programa ou um interpretador que executa código Javascript. Uma engine Javascript pode ser implementada como um interpretador padrão, ou apenas um compilador que na hora certa (Just-in-time) compila Javascript para bytecode de alguma forma.

A engine mais famosa do javascript é a V8 da Google que é a mesma utilizada pelo NodeJs, vamos começar por ela, abaixo uma representação simplificada.

engine js

A engine consiste de dois principais componentes:

Memory Heap — é onde a alocação de memória acontece.

Call Stack — onde seus stack frames (quadros de pilha) estão enquanto seu código é executado.

O Runtime ( tempo de execução )

Há APIs no browser que têm sido utilizadas por quase todo o desenvolvedor Javascript (exemplo: setTimeout ). Estas APIs, no entanto, não são fornecidas pela Engine.

Então, de onde elas vem?

Acontece que a realidade é um pouco mais complicada.

engine js 2

Então, temos a engine, mas na verdade tem muito mais. Temos essas coisas das quais chamamos Web APIs que são fornecidas pelos browsers, como o DOM, AJAX, setTimeout e muito mais.

E então, temos o tão popular event loop e o callback queue.

A Call Stack (Pilha de chamadas)

A Call Stack é uma estrutura de dados que armazena basicamente onde no programa nós estamos. Se entrarmos em uma função, nós colocamos ela sobre o topo da Stack. Se retornamos de uma função, saímos do topo da stack. Isso é tudo que a stack pode fazer.

Vamos ver um exemplo. Veja o seguinte código:

function multiply(x, y) {
  return x * y;
}
function printSquare(x) {
  var s = multiply(x, x);
  console.log(s);
}
printSquare(5);
Enter fullscreen mode Exit fullscreen mode

Quando a engine começa a executar esse código, a Call Stack vai estar vazia. Depois, os passos serão os seguintes:

engine js 3

Cada entrada na Call Stack é chamada de Stack frame.

E isso é exatamente como stack traces estão sendo construídos quando uma exceção é lançada — isso é basicamente o estado da Call Stack quando a exceção acontece. Dê uma olhada no seguinte código:

function foo() {
  throw new Error("throw error here ...");
}
function bar() {
  foo();
}
function start() {
  bar();
}
start();
Enter fullscreen mode Exit fullscreen mode

Se isso é executado no Chrome, o seguinte stack trace vai ser produzido:

stack trace

"Explodir a stack” — isso acontece quando você chega no tamanho máximo da Call Stack. E o que poderia acontecer muito facilmente, especialmente se você está utilizando recursão sem testar seu código muito extensivamente. Dê uma olhada neste exemplo de código:

function foo() {
  foo();
}
foo();
Enter fullscreen mode Exit fullscreen mode

Quando a engine começa a executar esse código, ela começa chamando a função “foo”. Essa função, no entanto, é recursiva e começa a chamar ela mesmo sem qualquer condição para terminar. Então em cada ponto da execução, a mesma função é adicionada para a Call Stack de novo e de novo. Isso parece alguma assim:

engine js 4

Em algum ponto, no entanto, o número de chamadas de função na Call Stack excede o tamanho atual da Call Stack, e o browser decide tomar uma ação, lançando um erro.

Executando código sobre um single-thread pode ser muito fácil desde que você não tenha que lidar com cenários complicados que são criados em ambientes multi-threaded — por exemplo, deadlocks.

Mas executando sobre single-thread é muito limitante também. Já que o Javascript tem uma única Call Stack, o que acontece quando as coisas estão lentas ?

Concorrência e o Event Loop

O que acontece quando você tem callback na Call Stack que tomam uma enorme quantidade de tempo a fim de ser processada? Por exemplo, imagine que você quer fazer alguma transformação complexa de imagem com Javascript no browser.

Você pode perguntar — Por que isso é um problema ? O problema é que enquanto a Call Stack tem funções para executar, o browser não pode fazer qualquer coisa mais — ele está bloqueado. Isso significa que o browser não pode renderizar, ele não pode executar qualquer outro código, está preso. E isso cria problemas se você quer UI fluídas em seu código.

E esse não é o único problema. Uma vez que o browser começa processar muitas tarefas na Call Stack, ele pode parar de responder por um longo tempo. E a maioria dos browsers tomam uma ação criando um erro, perguntando se você quer encerrar a página web.

error browser

Agora, essa não é a melhor experiência do usuário não é?

Então, como podemos executar códigos pesados ​​sem bloquear a interface do usuário e deixar o navegador sem resposta? Bem, a solução é callbacks assíncronos.

O Event Loop

A engine JS não é executada isoladamente, ela é executada dentro de um ambiente de hospedagem , que para a maioria dos desenvolvedores é o típico navegador da Web ou o Node.js. Atualmente, o JavaScript é incorporado em todos os tipos de dispositivos, desde robôs até lâmpadas. Cada dispositivo representa um tipo diferente de ambiente de hospedagem para a engine JS.

O denominador comum em todos os ambientes é um mecanismo interno chamado event loop, que lida com a execução de vários fragmentos de seu programa ao longo do tempo, sempre chamando o engine de JS.

Isso significa que a engine JS é apenas um ambiente de execução sob demanda para qualquer código JS arbitrário. É o ambiente circundante que agenda os eventos (as execuções do código JS).

O navegador é então configurado para escutar a resposta da rede, e quando tiver algo para retornar a você, ele agendará a função de retorno de chamada ( callback ) a ser executada inserindo-a no loop de eventos .

Vamos ver o diagrama abaixo:

event loop

E quais são essas APIs da Web? Em essência, eles são tópicos que você não pode acessar, você pode apenas fazer chamadas para eles. Eles são as partes do navegador nas quais a simultaneidade entra em ação. Se você é um desenvolvedor do Node.js, essas são as APIs do C++.

Então, o que é o event loop depois de tudo ?

event loop 2

O Event Loop tem um trabalho simples — para monitorar a call stack e a callback queue. Se a call stack estiver vazia, ela pegará o primeiro evento da fila e a empurrará para a call stack, que efetivamente a executa.

Tal iteração é chamada de tick no Event Loop. Cada evento é apenas uma função de callback.

Vamos “executar” esse código a seguir e ver o que acontece:

console.log("Hi");

setTimeout(function cb1() {
  console.log("cb1");
}, 5000);

console.log("Bye");
Enter fullscreen mode Exit fullscreen mode

event loop gif

  1. O estado é claro. O console do navegador é claro e a call stack está vazia.
  2. console.log('Hi') é adicionado à call stack.
  3. console.log('Hi') é executado.
  4. console.log('Hi') é removido da pilha de chamadas.
  5. setTimeout(function cb1() { ... })é adicionado à call stack.
  6. setTimeout(function cb1() { ... }) é executado. O navegador cria um cronometro como parte das APIs da Web. Ele vai lidar com a contagem regressiva para você.
  7. setTimeout(function cb1() { ... })está completo e é removido da call stack.
  8. console.log('Bye') é adicionado à call stack.
  9. console.log('Bye') é executado.
  10. console.log('Bye') é removido da call stack.
  11. Após pelo menos 5000 ms, o temporizador é concluído e ele envia o retorno de chamada cb1 para a fila de retorno de chamada.
  12. O Event Loop retira o cb1 da callback queue e o envia para a call stack.
  13. O cb1 é executado e adiciona o console.log('cb1')à call stack.
  14. console.log('cb1') é executado.
  15. O console.log('cb1') é removido da call stack.
  16. O cb1 é removido da call stack.

O ES6 especifica como o event loop deve funcionar, o que significa que, tecnicamente, ele está dentro do escopo das responsabilidades da engine JS, que não está mais desempenhando apenas uma função de ambiente de hospedagem.

Job Queue

No event loop existe uma fila que lida com micro task chamada Job Queue que trata caso de assíncronismo como Promise , enquanto as macro task é tratado pela Task Queue.

Aqui estão alguns pontos essenciais a serem observados:

  • Nem todas as tarefas são criadas com a mesma prioridade. Existem macrotarefas e microtarefas.
  • MacroTasks são chamados Tasks e MicroTasks são chamados Jobs.
  • Exemplos de macrotarefas são setTimeout, setInterval, setImmediate, I/O tasks, etc.
  • Exemplos de microtarefas são Promises, process.nextTick, etc.
  • A Fila no Loop de Eventos contém as Tasks ( ou as MacroTasks ) chamadas Task Queue.
  • A fila no loop de eventos contém os Jobs ( ou as microtasks ) chamados Job Queue.
  • Por exemplo, Promises estão em Job Queue e as funções para setTimeOut estão em Task Queue.

A Job Queue possui prioridade sobre a Task Queue no Event Loop.

No geral, o Event Loop ganhou mais um item a ser considerado em sua orquestração de Execução de Código. Vamos entender a lógica acima com um fluxo de execução de código.

const tom = () => console.log("Tom");

const jerry = () => console.log("Jerry");

const cartoon = () => {
  console.log("Cartoon");

  setTimeout(tom, 5000);

  new Promise((resolve, reject) =>
    resolve("should it be right after Tom, before Jerry?")
  ).then((resolve) => console.log(resolve));

  jerry();
};

cartoon();
Enter fullscreen mode Exit fullscreen mode

Portanto, a saída esperada é,

Cartoon
Jerry
should it be right after Tom, before Jerry?
Tom
Enter fullscreen mode Exit fullscreen mode

Fluxo de execução:

  • A função cartoon entra na call stack.
  • Ele executa o log do console do texto Cartoon. A setTimeOut API da Web sai da call stack na linha de execução a seguir e a função associada tom é colocada em TaskQueue.
  • Na seguinte linha de execução, encontramos uma Promise. Um callback de uma promise obtém um lugar na JobQueue. Portanto, a execução da função de log do console na promessa vai para JobQueue.
  • Na linha de execução a seguir, a função jerry é colocada na stack e executada.
  • Temos uma entrada em TaskQueue e outra em JobQueue. O event loop prioriza todos os Jobs em JobQueue sobre qualquer coisa em TaskQueue. Portanto, o callback da Promise chega primeiro à call stack, é executado e, em seguida, a função tom chega à call stack da TaskQueue e é executada.

E por aqui terminamos mais esse estudo de conceitos relacionado ao Javascript. Entender como funciona o Event Loop nos ajuda a criar códigos melhores, mais otimizados e é um conhecimento muito importante para quem quer se torna um programador acima da média.

Para se aprofundar mais no funcionamento interno do V8 e algoritmos de gerenciamento de memória acesse essa serie de artigos.

Este post tem como objetivo ajudar quem esta começando no aprendizado das tecnologias web, além de servir como incentivo no meus estudos e a criar outros posts pra fixação do aprendizado.

Me paga um café ? :) | pix: nascimento.dev.io@gmail.com

Me Sigam :)

Linkedin | Github

💖 💪 🙅 🚩
nascimento_
Jorge Nascimento

Posted on March 1, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related