O "Loop de eventos" do Node é o núcleo de sua capacidade de lidar com grande concorrência e alta taxa de transferência. Este é o lugar mais mágico. De acordo com isso, o Node.js pode basicamente ser entendido como "fibra única" e também permite que operações arbitrárias sejam processadas em segundo plano. Este artigo ilustrará como os loops de eventos funcionam e você também pode sentir sua mágica.
Programação orientada a eventos
Para entender os loops de eventos, devemos primeiro entender a programação do evento de eventos. Apareceu em 1960. Hoje, a programação orientada a eventos é amplamente utilizada na programação da interface do usuário. Um dos principais usos do JavaScript é interagir com o DOM, por isso é natural usar uma API baseada em eventos.
Basta definir: a programação orientada a eventos controla o processo de aplicação por meio de mudanças nos eventos ou estados. Geralmente é implementado através do monitoramento de eventos. Depois que o evento é detectado (ou seja, o estado muda), a função de retorno de chamada correspondente é chamada. Parece familiar? De fato, este é o princípio de trabalho básico do loop de eventos Node.js.
Se você estiver familiarizado com o desenvolvimento de JavaScript do lado do cliente, pense nesses métodos .on*(), como element.OnClick (), que são usados para combinar com os elementos DOM para passar a interação do usuário. Este modo de trabalho permite que vários eventos sejam acionados em uma única instância. O Node.js aciona esse modo através do EventEmitter (gerador de eventos), como no soquete e módulos "http" no lado do servidor. Uma ou mais alterações de estado podem ser acionadas de uma única instância.
Outro padrão comum é expressar sucesso e fracasso. Geralmente, existem dois métodos de implementação comuns agora. A primeira coisa é passar a "Exceção de erro" no retorno de chamada, que geralmente é passado para a função de retorno de chamada como o primeiro parâmetro. O segundo tipo é usar o modo de design de promessas e o ES6 foi adicionado. Nota* O modo Promise usa um método de redação de cadeia de funções do tipo jQuery para evitar nidificação de função de retorno de chamada profunda, como:
A cópia do código é a seguinte:
$ .getjson ('/getUser'). Done (succesthandler) .Fail (Failhandler)
Os módulos "FS" (sistema de arquivos) usam principalmente o estilo de passar exceções no retorno de chamada. Acionando tecnicamente certas chamadas, como o evento FSS.readfile () anexado, mas a API é usada apenas para lembrar o usuário a expressar sucesso ou falha da operação. Essa API é escolhida por razões arquitetônicas, não limitações técnicas.
Um equívoco comum é que os emissores de eventos também são inerentemente assíncronos ao desencadear eventos, mas isso está incorreto. Aqui está um snippet de código simples para provar isso.
A cópia do código é a seguinte:
function myemitter () {
EventEmitter.call (this);
}
util.Irits (Myemitter, EventEmitter);
Myemitter.prototype.dosff = função Dostuff () {
console.log ('antes')
emissor.emit ('fogo')
console.log ('depois')}
};
var Me = new Myemitter ();
me.on ('fogo', function () {
console.log ('emitido disparado');
});
me.dosfuff ();
// Saída:
// antes
// emiti disparado
// depois
Nota* Se emissor.emit for assíncrono, a saída deve ser
// antes
// depois
// emiti disparado
O EventEmitter geralmente se manifesta assíncrono porque é frequentemente usado para notificar operações que precisam ser concluídas de forma assíncrona, mas a própria API do EventEmitter é totalmente síncrona. A função de escuta pode ser executada de forma assíncrona, mas observe que todas as funções de escuta serão executadas de maneira síncrona na ordem em que são adicionadas.
Visão geral do mecanismo e agrupamento de threads
O próprio nó conta com várias bibliotecas. Um deles é Libuv, uma biblioteca que lida magicamente às filas e execuções de eventos assíncronos.
O nó usa o maior número possível de funções existentes para utilizar o kernel do sistema operacional. Por exemplo, uma solicitação de resposta é gerada, as conexões são encaminhadas e delegadas ao sistema para processamento. Por exemplo, as conexões recebidas são na fila através do sistema operacional até que possam ser processadas pelo nó.
Você deve ter ouvido saber que o Node tem um pool de threads e pode se perguntar: "Se o nó lidar com tarefas em ordem, por que você precisa de um pool de threads?" Isso ocorre porque no kernel, nem todas as tarefas são executadas de forma assíncrona. Nesse caso, o Node.js deve ser capaz de bloquear o thread por um período de tempo enquanto estiver em operação para que possa continuar a executar o loop do evento sem ser bloqueado.
A seguir, é apresentado um diagrama de exemplo simples para mostrar seu mecanismo de operação interno:
┌────── fara────── far uma das músicas
── Porta dos times │
│ └────── fara──┬─── far uma das músicas
│ ┌────── fara───── far uma
│ │ Retornos de chamada pendentes │
│ └────── farajuda┬──── far uma
│ ┌────── fare
│ │ Poll │◄──┤ conexões, │ │
│ └sto
│ ┌────── fara───── far uma coisa
──┤ SetImediate │
└────── fara──rig por causa
Existem algumas dificuldades em entender o mecanismo de operação interno do loop do evento:
Todos os retornos de chamada são predefinidos via process.NextTick () antes do final de uma fase do loop do evento (por exemplo, um temporizador) e a transição para a próxima fase. Isso evitará o potencial chamado recursivo ao processo.NextTick (), causando um loop infinito.
"Retorno de chamada pendente" é um retorno de chamada na fila de retorno de chamada que não será processado por nenhum outro ciclo de loop de eventos (por exemplo, passado para fs.write).
Emissor de eventos e loop de eventos
Ao criar o EventEmitter, a interação com os loops de eventos pode ser simplificada. É um encapsulamento universal que facilita a criação de APIs baseadas em eventos. Como os dois interagem geralmente fazem os desenvolvedores se sentirem confusos.
O exemplo a seguir mostra que o esquecimento de que o evento é acionado de forma síncrona pode fazer com que o evento seja perdido.
A cópia do código é a seguinte:
// após v0.10, requer ('eventos'). EventEmitter não é mais necessário
var eventEMitter = requer ('eventos');
var util = requer ('util');
function mything () {
EventEmitter.call (this);
Dofirstwork ();
this.emit ('Thing1');
}
utilits (mything, EventEmitter);
var mt = new Mything ();
Mt.on ('Thing1', function onthing1 () {
// desculpe, este incidente nunca acontecerá
});
O evento 'Thing1' acima nunca será pego por Mything (), porque o mything () deve ser instanciado antes que possa ouvir eventos. Aqui está uma solução alternativa simples sem precisar adicionar fechamentos extras:
A cópia do código é a seguinte:
var eventEMitter = requer ('eventos');
var util = requer ('util');
function mything () {
EventEmitter.call (this);
Dofirstwork ();
SetImediate (emitthing1, isto);
}
utilits (mything, EventEmitter);
função emitthing1 (self) {
self.emit ('Thing1');
}
var mt = new Mything ();
Mt.on ('Thing1', function onthing1 () {
// execute
});
O esquema a seguir também pode funcionar, mas algum desempenho está perdido:
A cópia do código é a seguinte:
function mything () {
EventEmitter.call (this);
Dofirstwork ();
// Usando a função#bind () perderá o desempenho
setImediate (this.emit.bind (this, 'Thing1'));
}
utilits (mything, EventEmitter);
Outro problema é desencadear um erro (exceção). Já é difícil descobrir o problema em seu aplicativo, mas sem a pilha de chamadas (nota *e.stack), a depuração é quase impossível. A pilha de chamadas será perdida quando o erro for solicitado pelo controle remoto de forma assíncrona. Existem duas soluções possíveis: acionamento síncrono ou garantindo que esse erro seja transmitido com outras informações importantes. O exemplo a seguir demonstra essas duas soluções:
A cópia do código é a seguinte:
Mything.prototype.foo = function foo () {
// Este erro será desencadeado de forma assíncrona
var er = dofirstwork ();
if (er) {
// Ao acionar, você precisa criar um novo erro que retém as informações da pilha de chamadas no local.
setImediate (emiterror, este, novo erro ('coisas ruins'));
retornar;
}
// aciona o erro e o processa imediatamente (sincronize)
var er = dosegundthing ();
if (er) {
this.emit ('erro', 'mais coisas ruins');
retornar;
}
}
Avalie a situação. Quando um erro é acionado, é possível ser processado imediatamente. Como alternativa, pode ser trivial e pode ser facilmente tratado ou manuseado mais tarde. Além disso, passar um erro através de um construtor não é uma boa idéia, porque a instância do objeto construída provavelmente será incompleta. A exceção é o caso em que o erro foi jogado diretamente agora.
Conclusão
Este artigo explora brevemente o mecanismo operacional interno e os detalhes técnicos do loop do evento. Todos são cuidadosamente considerados. Outro artigo discutirá a interação entre os loops de eventos e os kernels do sistema e demonstrará a magia dos nodejs em execução de forma assíncrona.