Conhecendo o fluxo de execução assíncrono em NodeJS

Algumas pessoas da área por diversas vezes me perguntam se eu entendo como funciona o fluxo de execução assíncrono. Para poder explicar isso de uma forma que fique mais clara vamos primeiro entender o que é fluxo de execução síncrono.

Fluxo de Execução Síncrono (Sync)
Fluxo de execução é  a ordem que as instruções  são executadas de um determinado programa.  Na programação tradicional (síncrona) isso é dado pela  sequência das instruções no conceito Top/Down existente no código fonte.

Para exemplificar segue um trecho de código em Javascript usando psudocódigo:

//Inicialização de um objeto chamado pessoa
var pessoa = {
   nome = "Leandro Curioso",
   idade: 28,
   genero:"masculino"
};

//Inicializa conexão com o banco de dados
var conexao = new Conexao("localhost","admin","123456","db_teste");
//Salva as informações em um arquivo
var salvarArquivoPessoa = function(pessoa, caminho){
   //Imagine que essa função save in file receba um objeto e salve em um arquivo json no caminho informado
   saveInFile(pessoa,caminho);
};

//Montar o caminho para salvar o arquivo
var caminhoArquivo = "/arquivos/pessoas/" + pessoa.nome + ".json";

//Chama a função salvar arquivo pessoa
salvarArquivoPessoa(pessoa,caminhoArquivo); 

//Após gerar o arquivo salva as informações também no banco de dados
conexao.inserir("tabela_pessoa",pessoa)

No conceito síncrono o código é executado da linha 1 até a linha 18, ou seja no Top/Down (de cima para baixo), não existe a possiblidade de pular nenhum trecho de código pois isso iria gerar um erro de execução. Pense nesse exemplo em um ambiente de alta concorrência (centenas ou milhares de requisições ao mesmo tempo) e mantenha isso em mente. Até o momento apenas um simples exemplo que você está acostumado a fazer em seu código php, C#, Java e etc..

Ai você me pergunta? OK entendi o fluxo síncrono mas qual a diferença dele para o assíncrono?

Eu te digo calma! Agora vamos entender o porque esse fluxo síncrono tem problemas quando falamos de um ambiente web com alta concorrência.  Existem alguns tipos de operação que são consideradas thread block (alguns chamam de thread lock) ou seja bloqueia a thread principal de execução do sistema até que seja finalizado o processamento da operação. Traduzindo isso para a web imagine que um usuário fez uma requisição no seu servidor e até que todas as operações que bloqueiam a thread sejam executadas nenhum outro usuário irá conseguir requisitar/receber dados do servidor. Grave não?

Para exemplificar seguem algumas operações que param sua thread principal:

  • I/O de disco em arquivos;
  • Instrução sleep que para a execução do programa por um tempo X;
  • Operação no banco de dados;
  • Envio de requisição http;

As barras vermelhas mostram quando o processo está esperando por um recurso da resposta externo estando bloqueadas.
As barras pretas mostram quando o código está rodando.
As barras verdes mostram o resto da aplicação.

Imagine um cenário onde sua aplicação web sendo um webservice ou servindo páginas http esteja com alta concorrência, milhares de usuários simultâneos indo no banco de dados, abrindo e inserindo informação dentro de arquivos, fazendo requisições em outros servidores. Concorda que isso se tornará um caos? Tente imaginar o tempo que levaria uma requisição do seu usuário passando por toda a concorrência para que possa receber uma resposta? TOTALMENTE INVIÁVEL! Dado o crescimento da web e suas aplicações a cada dia o fluxo assíncrono toma mais cena para contribuir na resolução desse problema computacional.

Certo! Pelo amor de Deus me explica o que é esse controle assíncrono?

Claro que explico agora que compartilhei contigo os problemas que a thread parada pode causar vamos falar da parte assíncrona.

Fluxo de Execução Assíncrono (Async)

O fluxo assíncrono é uma forma de processamento de input/output que permite outro processo continuar antes que a transmissão tenha finalizado.

O fluxo assíncrono diferente do síncrono não obedece exatamente a sequência das instruções do programa Top/Down. Para administrar isso é criado um loop de eventos que processa fora da ordem as instruções do programa que seguem na ilustração abaixo como exemplo INTENSIVE OPERATION.

Para ficar mais claro precisamos entender o que é e como funciona  o Event Loop:

O event loop é infinito e tem sua função como sendo uma espécie de serviço que fica constantemente lendo os eventos que estão na fila e os processando.  Esses eventos são inseridos na fila de acordo com o fluxo de execução do programa.

Então a inserção nessa fila de eventos não necessariamente segue um fluxo pré estabelecido contido no fonte do programa, pois imagine que na mesma aplicação existem milhares de usuários ao mesmo tempo. A possibilidade de que usuários estejam mandando informação ao servidor para ser processada na mesma janela de tempo é algo que com certeza irá acontecer, então a fila registra os eventos dos usuários independente da requisição pois existe uma promessa que ele será executado em algum momento. Para clarear mais o event loop armazena o que tem que ser processado em uma fila e vai executando em background sem dar o tão temido thread block que faz com que usuários fiquem esperando o I/O terminar para que possam requisitar/responder algum recurso.

Ai vem uma pergunta em sua cabeça que com certeza é bastante sagaz.  Curioso mas como ele armazena fora de ordem os eventos e sabe a hora de voltar a informação para o cliente que está pendurado na requisição aguardando essa tal promessa? Te respondo agora!

Toda vez que é emitido um evento para a fila do event loop ele deve presidir de um callback o qual é registrado por uma thread id que está ligada diretamente com o processo do cliente que aguarda o processamento.  Então temos:

  1. Processo entra na fila do event loop;
  2. Event loop lê o processo e registra seu callback;
  3. O processo é executado;
  4. É feito um disparo imediato do callback registrado para o processo do cliente para que possa dar continuidade na execução/resposta da operação.

Exemplo de função que irá se tornar um evento encadeada pelo callback.

//Exemplo de função com callback
var somar = function(n1,n2,callback){
    var total = n1 + n2;
    callback(total);
};

//Chamando a função somar e passando seu callback de retorno
somar(5,5,function(total){
   console.log(total); //Irá imprimir 10
});

O que dá para perceber é que nesse fluxo assíncrono foi criado uma forma alternativa e mais eficiente para lidar com a alta concorrência, pois dado o número de requisições e operações fica mais dinâmico e otimizado, permitindo que amenize o problemas dos bloqueios de thread e lide melhor com a alta concorrência.