Contents

O que é o loop de eventos e como ele melhora o desempenho do aplicativo?

O JavaScript é uma linguagem monoencadeada que opera por ordem de chegada no processamento de tarefas. O recurso de loop de eventos permite a manipulação assíncrona de eventos e retornos de chamada, simulando efetivamente recursos de multitarefa no ambiente JavaScript. Como tal, garante o desempenho ideal para qualquer aplicativo escrito com esta linguagem de programação.

O que é o loop de eventos do JavaScript?

O loop de eventos do JavaScript constitui um componente integral de todo e qualquer aplicativo JavaScript, facilitando o gerenciamento de tarefas de maneira sequencial e, ao mesmo tempo, permitindo a progressão desobstruída do thread de execução primário. Consequentemente, este fenômeno é comumente designado como “programação assíncrona”.

O loop de eventos mantém uma lista de operações a serem executadas, que ele envia sequencialmente à API da Web apropriada para processamento. O JavaScript supervisiona esse processo e gerencia a execução de cada operação com base em sua complexidade.

Para compreender a necessidade do loop de eventos do JavaScript e da programação assíncrona, é preciso entender o problema subjacente que ele aborda.

Pegue este código, por exemplo:

 function longRunningFunction() {
  // This function does something that takes a long time to execute.
  for (var i = 0; i < 100000; i\+\+) {
    console.log("Hello")
  }
}

function shortRunningFunction(a) {
  return a * 2 ;
}

function main() {
  var startTime = Date.now();
  longRunningFunction();
  
  var endTime = Date.now();

  // Prints the amount of time it took to execute functions
  console.log(shortRunningFunction(2));
  console.log("Time taken: " \+ (endTime-startTime) \+ " milliseconds");
}

main();

O presente código introduz uma função intitulada “longRunningFunction”. A referida função é projetada para realizar uma operação intrincada e demorada. Especificamente, ele emprega um loop “for” que itera cem mil vezes. Conseqüentemente, o comando “console.log(“Hello”)” é executado no mesmo número de iterações, resultando em cem mil instâncias da mensagem “Hello” sendo registradas no console.

O desempenho do programa pode ser afetado pela velocidade de processamento do computador, fazendo com que a execução de shortRunningFunction() seja atrasada até que a função anterior seja concluída.

As informações fornecidas comparam os tempos de execução de duas funções, que são as seguintes:

/pt/images/longfunction_console.jpg

E então o único shortRunningFunction():

/pt/images/shortfunction_console.jpg

A distinção entre um procedimento que leva 2.351 milissegundos para ser concluído e um que não leva nenhum tempo torna-se evidente quando se busca o desempenho ideal do aplicativo.

Como o loop de eventos ajuda no desempenho do aplicativo

O loop de eventos engloba uma série de fases e componentes interconectados, facilitando coletivamente a operação do sistema.

A Pilha de Chamadas

A pilha de chamadas JavaScript desempenha um papel crucial no gerenciamento de chamadas de funções e eventos em um aplicativo. Durante a compilação, o código JavaScript progride de cima para baixo. Por outro lado, quando o Node.js processa o código, ele começa na parte inferior e vai subindo, empurrando as definições de função para a pilha de chamadas, uma a uma, à medida que são encontradas.

A pilha de chamadas serve para preservar o contexto de execução e garantir a sequência adequada de chamadas de função, conseguindo isso por meio de sua implementação como uma estrutura LIFO (Last-In-First-Out).

A disposição das funções dentro de um programa é governada pela pilha, em que o item adicionado mais recentemente permanece no topo até ser removido ou executado. Conseqüentemente, ao executar o código JavaScript, o quadro de função final que é colocado na pilha de chamadas provavelmente será o inicial a ser retirado e executado em sequência, preservando assim a ordem desejada das operações.

/pt/images/callstackdiagram.jpg

A utilização de JavaScript resultará na remoção de todos os quadros da pilha até que seu conteúdo se esgote, o que significa que todas as funções concluíram sua execução.

Libuv Web API

No centro dos programas assíncronos do JavaScript está libuv. A biblioteca libuv é escrita na linguagem de programação C, que pode interagir com as APIs de baixo nível do sistema operacional. A biblioteca fornecerá várias APIs que permitem que o código JavaScript seja executado em paralelo com outro código. APIs para criar threads, uma API para comunicação entre threads e uma API para gerenciar a sincronização de threads.

Ao utilizar setTimeout em um ambiente Node.js, a biblioteca libuv subjacente assume a responsabilidade de supervisionar o loop de eventos e executar a função de retorno de chamada designada na expiração do intervalo de tempo predefinido.

Da mesma forma, ao conduzir operações de rede assíncronas, o libuv adota uma abordagem não obstrutiva, gerenciando tais tarefas de forma a permitir que outros processos prossigam sem impedimentos enquanto aguardam a conclusão do procedimento de entrada/saída (E/S).

O retorno de chamada e a fila de eventos

A fila de retorno de chamada e evento serve como uma área de espera para funções de retorno de chamada que estão antecipando a execução. Assim que uma tarefa assíncrona, iniciada por libuv, for concluída, sua respectiva função de retorno de chamada será colocada nesta fila para aguardar instruções adicionais.

Veja como fica a sequência:

Utilizando os recursos do libuv, o JavaScript transfere as operações assíncronas para o libuv para um gerenciamento eficiente, permitindo a progressão perfeita para a tarefa subsequente sem demora.

Após a conclusão de uma operação assíncrona em JavaScript, a função de retorno de chamada correspondente é adicionada a uma fila designada para tais tarefas.

A utilização do JavaScript permite a realização de diversas tarefas dentro de uma sequência pré-determinada, pois continua a executar as operações subseqüentes até que todas tenham sido concluídas em sua respectiva ordem.

Depois que todas as tarefas na pilha de chamadas forem processadas e não houver mais instruções a serem executadas, o JavaScript examinará a fila de retorno de chamada. A fila de retorno de chamada serve como uma área de espera para funções que precisam ser executadas posteriormente ou quando certas condições são atendidas. Ao revisar essa fila, o JavaScript pode garantir que lidará com todas as tarefas pendentes antes de encerrar sua operação.

Se algum retorno de chamada estiver presente na fila, ele será enviado para o topo da pilha de chamadas e executado de acordo.

Ao utilizar uma abordagem assíncrona, o thread principal permanece desobstruído durante a execução de operações de não manutenção, com a fila de retorno de chamada garantindo uma execução cronológica das funções de retorno de chamada associadas após a conclusão.

O Ciclo do Loop de Eventos

/pt/images/eventloopdiagram.jpg

O loop de eventos incorpora uma fila específica conhecida como fila de microtarefas, projetada para acomodar microtarefas programadas para execução imediata após a conclusão da tarefa atualmente ativa na pilha de chamadas. Essas tarefas são executadas antes de qualquer renderização ou iteração subsequente do loop de eventos. As microtarefas têm uma prioridade mais alta do que as tarefas padrão dentro do loop de eventos e têm precedência sobre elas.

Ao utilizar Promises na programação, uma abordagem comum envolve a criação de microtarefas. Após a resolução ou rejeição de uma Promise, seus respectivos retornos de chamada**.then() ou.catch()** são adicionados à fila de microtarefas. Essa fila pode ser empregada para gerenciar tarefas que requerem atenção imediata após a operação em andamento, incluindo atualizações nos componentes da interface do usuário e modificações no estado do aplicativo.

Uma instância ilustrativa de tal aplicativo da web envolve usuários interagindo com ele por meio de uma interface gráfica do usuário (GUI), na qual eles podem executar ações como clicar em botões para solicitar informações de um servidor de maneira não bloqueante. Esse processo envolve o sistema executando uma tarefa assíncrona a cada pressionamento de botão, que então recupera os dados remotamente sem interromper outras operações.

A operação do loop de eventos para esta tarefa específica pode ser entendida na ausência de microtarefas considerando a seguinte sequência de eventos:

⭐O usuário clica no botão repetidamente.

Ao clicar em um botão, uma ação de recuperação de dados sem bloqueio é iniciada.

Após a conclusão das operações de busca de dados em JavaScript, as funções de retorno de chamada associadas são adicionadas à fila de tarefas padrão para processamento.

A iniciação do loop de eventos envolve a manipulação de tarefas dentro da fila de tarefas padrão, caracterizada por sua maneira comum e habitual de operação.

A execução da atualização da interface do usuário, que depende dos resultados do processo de recuperação de dados, ocorre quando as tarefas habituais concedem permissão para seu início.

As microtarefas operam sob um mecanismo distinto dentro do loop de eventos. A abordagem padrão seguida por tarefas de macro não é aplicável a elas.

Ao clicar no botão designado, uma solicitação de dados é iniciada de forma assíncrona pelo usuário, dando início a uma série de operações.

Após a recuperação bem-sucedida dos dados, o loop de eventos prossegue para enfileirar as respectivas funções de retorno de chamada na fila de microtarefas para execução.

O loop de eventos foi projetado para lidar com tarefas com eficiência, priorizando-as com base em seus respectivos tipos, como tarefas de interface do usuário e microtarefas. Depois que uma tarefa for concluída, como clicar em um botão, o loop de eventos passará automaticamente para processar quaisquer microtarefas pendentes na fila sem demora.

A execução da atualização da interface do usuário em resposta ao resultado da recuperação de dados garante uma experiência de usuário rápida e reativa, precedendo a operação de rotina subsequente.

Aqui está um exemplo de código:

 const fetchData = () => {
  return new Promise(resolve => {
    setTimeout(() => resolve('Data from fetch'), 2000);
  });
};

document.getElementById('fetch-button').addEventListener('click', () => {
  fetchData().then(data => {//This UI update will run before the next rendering cycle
    updateUI(data);
  });
});

Ao clicar no botão “Fetch” neste caso, a função fetchData() é invocada. Após a recuperação dos dados, o processo é agendado como uma microtarefa. Após a conclusão da tarefa de aquisição de dados, a atualização da interface do usuário ocorre sem demora, precedendo qualquer renderização adicional ou atividades de loop de eventos.

Para evitar a latência causada por outras tarefas em andamento no loop de eventos, essa abordagem garante que os usuários recebam informações atualizadas prontamente.

A incorporação de microtarefas em aplicativos, como este, pode atenuar a intermitência da interface do usuário (IU) ou as interrupções conhecidas como “jank”, resultando em interações mais rápidas e contínuas para os usuários.

Implicações do loop de eventos para o desenvolvimento da Web

Obter uma compreensão profunda do loop de eventos e alavancar suas funcionalidades é fundamental para a construção de sistemas de software reativos e de alto desempenho. O loop de eventos oferece habilidades assíncronas e simultâneas, permitindo assim a execução criteriosa de operações intrincadas em um aplicativo, preservando a experiência ideal do usuário.

O Node.js oferece uma solução abrangente, abrangendo web workers que facilitam o paralelismo adicional além do contexto primário de execução do JavaScript.