loop de eventos no nó js

Loop De Eventos No No Js



Node.js é uma estrutura Javascript poderosa que permite aos usuários executar código Javascript no servidor fora do navegador. É um ambiente de execução não bloqueador e orientado a eventos para a construção de aplicativos da Web escalonáveis ​​e confiáveis. O loop de eventos é uma parte importante do Node.js que permite executar tarefas sem esperar que uma termine antes de iniciar outra.

Embora Javascript seja uma linguagem de thread único, o Node.js pode atribuir tarefas ao sistema operacional, permitindo-lhe processar várias tarefas ao mesmo tempo. Várias tarefas devem ser concluídas ao mesmo tempo porque as operações no sistema operacional são multithread. O retorno de chamada associado a cada operação é adicionado à fila de eventos e agendado pelo Node.js para ser executado quando a tarefa especificada for concluída.

Para escrever código Node.js eficiente e confiável, o usuário deve ter um conhecimento sólido de loops de eventos. Também pode ajudar na solução eficaz de problemas de desempenho. O loop de eventos no Node.js economiza memória e permite que você faça várias coisas ao mesmo tempo, sem ter que esperar que cada uma delas termine. O termo “assíncrono” refere-se a qualquer função Javascript executada em segundo plano sem bloquear solicitações recebidas.







Antes de pular diretamente para os loops de eventos, vamos dar uma olhada nos diferentes aspectos da linguagem de programação Javascript.



Javascript como linguagem de programação assíncrona

Vamos dar uma olhada nos conceitos de programação assíncrona. Javascript é usado em aplicativos da web, móveis e desktop, mas deve-se observar que Javascript é uma linguagem de programação de computador síncrona e de thread único.



Um exemplo de código simples é fornecido para entender o conceito.





método de função1 ( ) {

console. registro ( 'Função 1' )

}

método de função2 ( ) {

console. registro ( 'Função 2' )

}

Método 1 ( )

método2 ( )

Neste código, duas funções simples são criadas e o método1 é chamado primeiro, para que ele registre o método1 primeiro e depois passe para o próximo.

Saída



Javascript como linguagem de programação síncrona

Javascript é uma linguagem de programação síncrona e executa cada linha passo a passo, movendo-se de cima para baixo, com apenas uma linha executada por vez. No código de exemplo fornecido acima, o método1 é registrado primeiro no terminal e depois no método2.

Javascript como linguagem de bloqueio

Por ser uma linguagem síncrona, o javascript possui uma funcionalidade de bloqueio. Não importa quanto tempo leva para concluir um processo em andamento, mas um novo processo não será iniciado até que o anterior seja concluído. No exemplo de código acima, suponha que haja muitos scripts de código no método1, não importa quanto tempo leve 10 segundos ou um minuto, o método2 não será executado até que todo o código no método1 tenha sido executado.

Os usuários podem ter experimentado isso durante a navegação. Quando um aplicativo da web é executado em um navegador no back-end, um grande pedaço de código está sendo executado, de modo que o navegador parece estar congelado por algum tempo antes de retornar o acesso de controle ao usuário. Esse comportamento é conhecido como bloqueio. O navegador não pode receber mais solicitações recebidas até que a solicitação atual seja processada.

Javascript é uma linguagem de thread único

Para executar um programa em javascript, a funcionalidade thread é usada. Threads só são capazes de executar uma tarefa por vez. Outras linguagens de programação suportam multithreading e podem executar várias tarefas em paralelo; o javascript contém apenas um thread para executar qualquer script de código.

Esperando em Javascript

Como fica evidente pelo nome nesta seção, temos que esperar que nossa solicitação seja processada para prosseguir. A espera pode levar vários minutos durante os quais nenhuma solicitação adicional é atendida. Se o script do código prosseguir sem esperar, o código encontrará um erro. Algumas funcionalidades devem ser implementadas em Javascript ou mais especificamente em Node.js para tornar o código assíncrono.

Agora que entendemos os diferentes aspectos do Javascript, vamos entender o síncrono e o assíncrono por meio de alguns exemplos simples.

Execução síncrona de código em Javascript

Síncrono significa que o código é executado sequencialmente ou mais simplesmente passo a passo, começando do topo e descendo linha por linha.

Abaixo é dado um exemplo que pode ajudar a entender:

//aplicativo.js

console. registro ( 'Um' )

console. registro ( 'Dois' )

console. registro ( 'Três' )

Neste código, existem três instruções console.log, cada uma imprimindo algo. Primeiramente, a primeira instrução que vai imprimir “One” no console é enviada para a pilha de chamadas por 1 ms (estimado) e depois é registrada no terminal. Depois disso, a segunda instrução é colocada na pilha de chamadas e agora o tempo é de 2 ms com uma adicionada da anterior e então registra “Dois” no console. Finalmente, a última instrução é colocada na pilha de chamadas, pois agora o tempo é de 3 ms e registra “Três” no console.

O código acima pode ser executado invocando o seguinte comando:

aplicativo de nó. js

Saída

O funcionamento é explicado detalhadamente acima e, levando-o em consideração, a saída é registrada no console em um piscar de olhos:

Execução assíncrona de código em Javascript

Agora vamos refatorar o mesmo código introduzindo retornos de chamada e tornando o código assíncrono. O código acima pode ser refatorado como:

//aplicativo.js
função printOne ( ligar de volta ) {
setTimeout ( função ( ) {
console. registro ( 'Um' ) ;
ligar de volta ( ) ;
} , 1000 ) ;
}
função printTwo ( ligar de volta ) {
setTimeout ( função ( ) {
console. registro ( 'Dois' ) ;
ligar de volta ( ) ;
} , 2000 ) ;
}
função imprimirTrês ( ) {
setTimeout ( função ( ) {
console. registro ( 'Três' ) ;
} , 3.000 ) ;
}
console. registro ( 'Início do programa' ) ;
imprimirUm ( função ( ) {
imprimir dois ( função ( ) {
imprimirTrês ( ) ;
} ) ;
} ) ;
console. registro ( 'Fim do programa' ) ;

Neste código acima:

  • Três funções são declaradas para imprimir “Um”, “Dois” e “Três”, cada função possui um parâmetro de retorno de chamada que permite a execução sequencial do código.
  • Um tempo limite é definido usando a função setTimeout e há uma instrução console.log para impressão após um atraso específico.
  • São impressas duas mensagens “Início do Programa” e “Fim do Programa” que indicam o início e o fim do programa.
  • O programa começa imprimindo “Início do Programa” após o qual a função printOne é executada com um atraso de 1 segundo, então a função printTwo é executada com um atraso de 2 segundos e, finalmente, a função printThree é executada com um atraso de 3 segundos. atraso.
  • O programa não espera pelas execuções de código assíncrono dentro das funções setTimeouts que registram a instrução “Fim do Programa” antes de imprimir Um, Dois e Três.

Saída

Execute o código acima executando este comando no terminal:

aplicativo de nó. js

Agora a saída no terminal apareceria de forma assíncrona como:

Agora que temos uma compreensão completa da execução síncrona e assíncrona, vamos solidificar nosso conceito de loop de eventos em Node.js.

Node.js: mecanismo de loop de eventos

A execução de tarefas síncronas e assíncronas é gerenciada pelo loop de eventos no Node.js. A execução é invocada assim que o projeto Node.js é iniciado e transfere suavemente as tarefas complexas para o sistema. Isso garante que outras tarefas possam ser executadas sem problemas no thread principal.

Explicação visual do loop de eventos em Node.js

O loop de eventos é contínuo e semi-infinito em Node.js. O loop de eventos é invocado pelo início do script de código Node.js e é responsável por fazer chamadas de API assíncronas e chamar processos.Tick(), e agendar cronômetros e então retomar a execução do loop de eventos.

No Node.js, cinco tipos principais de filas lidam com retornos de chamada:

  • A “Fila do temporizador” comumente conhecida como min-heap é responsável por lidar com retornos de chamada associados a “setTimeout” e “setInterval”.
  • Os retornos de chamada para operações assíncronas como nos módulos “fs” e “http” são tratados pela “Fila de E/S”.
  • O “Check Queue” contém retornos de chamada para a função “setImmediate” que é exclusiva do Node.
  • O “Close Queue” gerencia retornos de chamada associados a qualquer evento de fechamento de tarefa assíncrona.
  • Por último, existem duas filas diferentes na fila “Micro Task”:
    • A fila “nextTick” contém retornos de chamada associados à função “process.nextTick”.
    • A fila “Promise” controla retornos de chamada relacionados ao Promise nativo.

Funcionalidade de loop de eventos em Node.js

O loop de eventos funciona sob requisitos específicos que controlam a ordem de execução do retorno de chamada. O código Javascript síncrono do usuário tem prioridade no início do processo, portanto, o loop de eventos só começa quando a pilha de chamadas é limpa. A sequência de execução a seguir segue um padrão estruturado:

A prioridade mais alta é dada aos retornos de chamada na fila de microtarefas, passando então para executar as tarefas na fila nextTick seguidas pelas tarefas na fila Promise. Os processos nos retornos de chamada da fila do cronômetro são então tratados, após o que a fila de microtarefas é visitada novamente após cada retorno de chamada do cronômetro. Os retornos de chamada nas filas de E/S, verificação e fechamento são então executados em um padrão semelhante à fila de microtarefas visitada após cada fase.

O loop continua a ser executado se houver mais retornos de chamada para processar. Quando o script de código terminar ou nenhum retorno de chamada for processado, o loop de eventos terminará de forma eficiente.

Agora que entendemos profundamente o loop de eventos, vamos dar uma olhada em seus recursos.

Recursos de loop de eventos em Node.js

As principais características são:

  • O loop de eventos é um loop infinito e continua a executar as tarefas assim que as recebe e entra em modo sleep caso não haja tarefas, mas começa a funcionar assim que a tarefa é recebida.
  • As tarefas na fila de eventos são executadas apenas quando a pilha está vazia, o que significa que não há operação ativa.
  • Retornos de chamada e promessas podem ser usados ​​no loop de eventos.
  • Como o loop de eventos segue o princípio da fila do tipo de dados abstrato, ele cumpre a primeira tarefa e depois prossegue para a próxima.

Após uma compreensão completa do loop de eventos e da lógica das execuções assíncronas e síncronas, a compreensão das diferentes fases pode solidificar os conceitos do loop de eventos.

Fases do loop de eventos Node.js

Como mencionado acima, o loop de eventos é semi-infinito. Possui muitas fases, mas algumas fases são usadas para manuseio interno. Essas fases não têm nenhum efeito no script do código.

O loop de eventos segue a funcionalidade do Queue e executa a tarefa com base no princípio de primeiro a entrar e primeiro a sair. Os cronômetros programados serão gerenciados pelo sistema operacional até expirarem. Os temporizadores expirados são então adicionados à fila de retorno de chamada para temporizadores.

O loop de eventos executa as tarefas na fila do cronômetro, uma por uma, até que não haja mais tarefas restantes ou atinja o número máximo permitido de tarefas. Nas seções abaixo, as fases principais dos loops de eventos são explicadas.

Fase dos temporizadores

No Node.js existe uma API de timer que pode agendar as funções que serão executadas no futuro. Depois que o tempo alocado tiver passado, o retorno de chamada do timer será executado assim que puder ser agendado; entretanto, um atraso pode ocorrer no final do sistema operacional ou devido à execução de outros retornos de chamada.

A API de temporizadores tem três funções principais:

  • setTimeout
  • definirImediato
  • setInterval

As funções mencionadas acima são síncronas. A fase do temporizador no loop de eventos tem seu escopo limitado às funções setTimeout e setInterval. Enquanto a função check lida com a função setImmediate.

Vamos considerar um exemplo simples para solidificar a parte teórica:

//aplicativo.js

função atrasadaFunction ( ) {

console. registro ( 'a função atrasada é executada após o tempo limite' ) ;

}

console. registro ( 'Início do programa' ) ;

setTimeout ( atrasadoFunção, 2000 ) ;

console. registro ( 'Fim do programa' ) ;

Neste código:

  • O programa começa registrando a instrução “Início do Programa” no terminal.
  • Em seguida, o delayFunction é chamado com um temporizador de 2 ms, o script de código não para e continua lidando com o atraso em segundo plano.
  • A instrução “Fim do Programa é registrada após a primeira instrução.
  • Após um atraso de 2 ms, a instrução em delayFunction é registrada no terminal.

Saída

A saída aparecerá como:

Pode-se observar que o código não é interrompido para que o delayFunction seja processado; ele avança e após o atraso, o retorno de chamada da função é processado.

Retornos de chamada pendentes

O loop de eventos verifica os eventos que estão acontecendo, como leitura de arquivos, atividades de rede ou tarefas de entrada/saída, na fase de pesquisa. É importante saber que, no Node.js, apenas alguns dos eventos são tratados nesta fase de polling. Contudo, na iteração subsequente do loop de eventos, certos eventos podem ser adiados para a fase pendente. Este é um conceito importante a ser lembrado ao otimizar e solucionar problemas de código Node.js que envolve operações complexas orientadas a eventos.

É importante entender que durante a fase de espera de retornos de chamada, o loop de eventos adiciona eventos adiados à fila de retornos de chamada pendentes e os executa. Esta fase também trata alguns erros de soquete TCP gerados pelo sistema, como eventos de erro ECONNREFUSED em determinados sistemas operacionais.

Abaixo é mencionado um exemplo para solidificar o conceito:

//aplicativo.js
const fs = exigir ( 'fs' ) ;
função readFileAsync ( filePath, retorno de chamada ) {
fs. arquivo lido ( './PromiseText.txt' , 'utf8' , função ( err, dados ) {
se ( errar ) {
console. erro ( ` Erro lendo arquivo : $ { errar. mensagem } ` ) ;
} outro {
console. registro ( ` Arquivo contente : $ { dados } ` ) ;
}
ligar de volta ( ) ;
} ) ;
}
console. registro ( 'Início do programa' ) ;
readFileAsync ( './PromiseText.txt' , função ( ) {
console. registro ( 'Retorno de chamada de leitura de arquivo executado' ) ;
} ) ;
console. registro ( 'Fim do programa' ) ;

Neste código:

  • O programa é iniciado registrando a instrução “Início do Programa” no terminal.
  • O readFileAsync é definido de forma assíncrona para ler o conteúdo do arquivo “PromiseText.txt”. É uma função parametrizada que executa uma função de retorno de chamada após a leitura do arquivo.
  • A função readFileAsync é chamada para iniciar o processo de leitura do arquivo.
  • No processo de leitura do arquivo, o programa não para; em vez disso, ele prossegue para a próxima instrução e registra-a no terminal “Fim do Programa”.
  • O evento assíncrono de leitura de arquivo é processado em segundo plano pelo loop de eventos.
  • Depois que o arquivo foi lido de forma assíncrona e o conteúdo foi registrado no terminal, o programa registra o conteúdo do arquivo no terminal. Depois disso, ele registra a seguinte mensagem “Retorno de chamada de leitura de arquivo executado”.
  • O loop de eventos trata das operações de retorno de chamada pendentes na próxima fase.

Saída

O resultado da execução acima é:

Ocioso, fase de preparação em Node.js

A fase inativa é usada para lidar com funções internas no Node.js, portanto não é uma fase padrão. Isso não influencia o script do código. A fase inativa é como um período de pausa para o loop de eventos durante o qual gerencia as tarefas de baixa prioridade em segundo plano. Um exemplo simples para entender esta fase é:

const { parado } = exigir ( 'idle-gc' ) ;

parado. ignorar ( ) ;

Neste código é utilizado o módulo “idle-gc” que permite ignorar a fase inativa. Isso serve para lidar com situações em que o loop de eventos está ocupado e as tarefas em segundo plano não são executadas. O uso de idle.ignore não é considerado ideal, pois pode causar problemas de desempenho.

Fase de pesquisa em Node.js

A fase de enquete no Node.js serve como:

  • Ele lida com os eventos na fila de votação e executa as tarefas correspondentes.
  • Ele decide quanto tempo gastar esperando e verificando as operações de E/S no processo.

À medida que o loop de eventos entra na fase de pesquisa devido à ausência de um temporizador, uma das tarefas abaixo será executada:

  • Na fase de pesquisa do loop de eventos em Node.js, os eventos de E/S pendentes são enfileirados e então executados em um procedimento sequencial seguindo o princípio de Primeiro a Entrar e Primeiro a Sair até que a fila fique vazia. Durante as execuções de callbacks as filas nextTick e microtasks também estão em ação. Isso garante suavidade e permite lidar com operações de E/S de forma mais eficiente e confiável.
  • Se a fila estiver vazia e o script não tiver sido agendado pela função setImmediate() então o loop de eventos terminará e prosseguirá para a próxima fase (verificação). Por outro lado, se o agendamento do script tiver sido feito pela função setImmediate() o loop de eventos permite que os callbacks sejam adicionados à fila que será executada por ele.

Isso é melhor ilustrado com um exemplo de código simples:

setTimeout ( ( ) => {

console. registro ( 'Operação assíncrona concluída' ) ;

} , 2000 ) ;

console. registro ( 'Começar' ) ;

definirImediato ( ( ) => {

console. registro ( 'setImmediate retorno de chamada executado' ) ;

} ) ;

console. registro ( 'Fim' ) ;

Neste código:

  • Duas mensagens “Início” e “Fim” indicam o início e o término do programa.
  • A função setTimeout() define uma função de retorno de chamada com um atraso de 2 ms e registra “Operação assíncrona concluída” no terminal.
  • A função setImmediate() registra a mensagem “setImmediate callback executado” no terminal após a mensagem Iniciar ter sido registrada no terminal.

Saída

A saída mostraria as mensagens com apenas um minuto de observação de que a “operação assíncrona concluída” leva tempo e é impressa após a mensagem “Fim”:

Fase de verificação do Node.js

Após a execução da fase de pesquisa, os retornos de chamada na fase de verificação são executados. Se um script de código for agendado usando a função setImmediate() e a função poll for gratuita, o loop de eventos funciona movendo-se diretamente para a fase de verificação em vez de permanecer ocioso. A função setImmediate() é um temporizador exclusivo que opera durante as diferentes fases do loop de eventos.

A API libuv é usada para planejar as execuções de retorno de chamada após a conclusão da execução da fase de pesquisa. Durante a execução do código, o loop de eventos entra na fase de pesquisa na qual aguarda as solicitações de conexão recebidas. Em outro caso, se o retorno de chamada for agendado usando a função setImmediate() e a fase de pesquisa for encerrada sem qualquer atividade, ele passará para a fase de verificação em vez de esperar. Considere o exemplo abaixo para compreensão:

//aplicativo.js

console. registro ( 'Começar' ) ;

definirImediato ( ( ) => {

console. registro ( 'Retorno de chamada imediato' ) ;

} ) ;

console. registro ( 'Fim' ) ;

Neste código, três mensagens são registradas no terminal. A função setImmediate() finalmente envia um retorno de chamada para registrar a mensagem “ Retorno de chamada imediato ”Para o terminal.

Saída

A saída do código acima aparecerá na seguinte sequência:

Node.js fecha retornos de chamada

O Node.js usa essa fase de fechamento para executar retornos de chamada para fechar eventos e encerrar uma iteração de loop de eventos. Após o fechamento da conexão, o loop de eventos trata dos eventos de fechamento nesta fase. Nesta fase do loop de eventos, “nextTick()” e microtarefas são gerados e processados ​​de forma semelhante a outras fases.

A função process.exit é usada para encerrar o loop de eventos a qualquer instante. O loop de eventos desconsiderará quaisquer operações assíncronas pendentes e o processo Node.js será encerrado.

Um exemplo simples a considerar é:

//aplicativo.js
const líquido = exigir ( 'líquido' ) ;
const servidor = líquido. criarServidor ( ( tomada ) => {
soquete. sobre ( 'fechar' , ( ) => {
console. registro ( 'Soquete fechado' ) ;
} ) ;
soquete. sobre ( 'dados' , ( dados ) => {
console. registro ( 'Dados recebidos:' , dados. para sequenciar ( ) ) ;
} ) ;
} ) ;
servidor. sobre ( 'fechar' , ( ) => {
console. registro ( 'Servidor fechado' ) ;
} ) ;
const porta = 3.000 ;
servidor. ouvir ( porta, ( ) => {
console. registro ( `Servidor escutando na porta $ { porta } ` ) ;
} ) ;
setTimeout ( ( ) => {
console. registro ( 'Fechando servidor após 10 segundos' ) ;
servidor. fechar ( ) ;
processo. saída ( ) ;
} , 10.000 ) ;

Neste código:

  • const rede = exigir('rede') ”importa o módulo de rede necessário para lidar com um servidor TCP e“ servidor const = net.createServer((socket) => { ”cria uma nova instância do servidor TCP.
  • socket.on('fechar', () => {… } ” ouve o “fechamento” em todos os soquetes. Quando a conexão do soquete é fechada, a mensagem “Socket Closed” é registrada no terminal.
  • socket.on('dados', (dados) => {} ” verifica os dados recebidos de todos os soquetes individuais e os imprime usando a função “.toString()”.
  • server.on('fechar', () => {…} ” verifica o evento “fechar” no próprio servidor e, quando a conexão do servidor é encerrada, registra a mensagem “Servidor Fechado” no terminal.
  • server.listen(porta, () => {…} ”escuta conexões de entrada na porta.
  • setTimeout(() => {…} ” define um temporizador de 10 ms para fechar o servidor.

Isso conclui a discussão sobre as várias fases do loop de eventos no Node.js. Antes de tirar uma conclusão, vamos discutir uma última coisa: como sair do loop de eventos no Node.js.

Saindo do loop de eventos em Node.js

O loop de eventos está em fase de execução enquanto houver algumas tarefas em todas as filas das fases do loop de eventos. O loop de eventos termina após a emissão da fase de saída e o retorno de chamada do ouvinte de saída retorna se não houver mais tarefas nas filas.

A maneira explícita de encerrar um loop de eventos é usar o método “.exit”. Os processos ativos do Node.js serão encerrados instantaneamente assim que a função process.exit for chamada. Todos os eventos agendados e pendentes serão descartados:

processo. sobre ( 'saída' , ( código ) => {

console. registro ( `Saindo com código de saída : $ { código } ` ) ;

} ) ;

processo. saída ( 1 ) ;

Os usuários podem ouvir a função .exit. Deve-se observar que a função “.exit” deve ser síncrona, pois o programa Node.js será encerrado assim que ouvir este evento.

Isso conclui a discussão sobre o loop de eventos. Um artigo aprofundado que cobriu todos os conceitos, fases e exemplos relacionados ao loop de eventos.

Conclusão

Antes de compreender o loop de eventos, uma visão geral dos conceitos síncronos e assíncronos pode ajudar a compreender o fluxo de código no loop de eventos. A execução síncrona significa execução passo a passo, enquanto a execução assíncrona significa interromper algumas etapas sem esperar pela sua conclusão. O funcionamento do loop de eventos junto com todas as fases, juntamente com exemplos adequados, são discutidos no artigo.