O que é o Node.js?
Diferente do que muitos pensam o Node.js não é uma linguagem de programação, e sim uma plataforma (runtime) que permite rodar código JavaScript (que no caso é uma linguagem de programação) no Servidor.
Bom, inicialmente o JavaScript foi criado para rodar apenas em Browsers, porém em 2008 foi lançada uma nova versão do Google Chrome e junto com essa nova versão veio o V8 que é um interpretador de código JavaScript.
E em 2009, um engenheiro de software chamado Ryan Dahl executou o V8 no Servidor, ou seja, fez com que o código JavaScript fosse interpretado no lado do Servidor em vez do lado do Cliente (Browsers), e com isso nasceu o que a gente conhecem hoje como Node.js.
Como o Node.js executa nosso código?
Vamos começar entendendo sobre Call Stack, Thread Pool, Event Loop e Callback Queue.
- Call Stack
A call stack é a pilha de processamento do nosso código, ou seja, todas as funções que executarmos irão para essa pilha (stack).
Vamos a um exemplo prático da Call Stack e vamos analisar o código abaixo:
function multiply(x, y) {
return x * y;
}
function printSquare(x) {
const square = multiply(x, x);
console.log(square);
}
printSquare(5);
Ao rodarmos o código acima, o JavaScript irá ler de cima para baixo, declarar as duas funções (multiply
e printSquare
) e ao final de tudo, irá invocar a função printSquare
passando o valor 5 no argumento.
Veja a imagem abaixo para melhor compreensão.
- Step 1.
Quando invocamos a função
printSquare
, é aqui que entra a nossa Call Stack em ação, como mencionei anteriormente, todas as funções que executamos no nosso código vão para a nossa call stack (pilha de processamento), então a função printSquare já está lá.
- Step 2.
Dentro da função
printSquare
, a gente chama na primeira linha a funçãomultiply
, então a funçãomultiply
, também entra na pilha de chamadas.
- Step 3.
Bem, dentro da função
multiply
não vamos mais a nenhum lugar, não chamamos nenhuma outra função ou algo do tipo, simplesmente pegamos os argumentos x e y multiplicamos um pelo outro e retornamos o resultado, então a partir do momento que a funçãomultiply
está retornando o resultado, o papel dela é concluído e ela sai da nossa pilha de chamadas.E agora, voltando para dentro da função
printSquare
, chamamos umconsole.log()
para mostrar o resultado, então a funçãoconsole.log
entra na nossa pilha de chamadas, é executada e depois sai da pilha.
- Step 4 & 5:
E agora, como não temos mais nada para fazer dentro da função
printSquare
, o processamento do nosso código acabou e a funçãoprintSquare
também sai da nossa call stack.
Bom, com isso já conseguimos notar uma característica da Call Stack: A primeira função que entra é a última que sai.
A call stack tem muito peso em nosso código e por isso é muito importante sabermos sobre sua existência e entender como ela funciona.
- Single Thread
Outra característica do Node.js é que ele é Single Thread, ou seja, só pode executar uma tarefa por vez.
“Mas como assim ele só executa uma tarefa por vez, ouço todo mundo falando que o Node.js é Non-Blocking I/O, ou seja, tem entrada e saída de dados de forma não bloqueante”.
De fato, o Node.js não pode executar mais de uma tarefa por vez, temos apenas 1 call stack, e enquanto ela estiver ocupada, não podemos executar mais nada.
“Então, por que falam que o Node.js é Non-Blocking I/O e que pode executar mais de uma tarefa ao mesmo tempo?”
Porque de fato ele é Non-Blocking I/O e também de fato ele pode executar mais de uma tarefa ao mesmo tempo, e ele consegue fazer isto mesmo sendo Single Thread, porque por debaixo dos panos ele roda em uma biblioteca escrita em C chamada libuv.
A libuv implementa o Thread Pool e o Event Loop, também implementa toda a parte do filesystem do Node, DNS e etc. A libuv é uma biblioteca muito completa e dá muitos poderes ao Node.
Se quiser conhecer mais sobre a libuv, aqui estão alguns links uteis:
Site oficial
Github
Libuv no Node.js
Bem, vamos prosseguir, considere o seguinte código:
console.log('Before.');
db.query(query, function callback(error, data) {
console.log(data);
});
console.log('After.')
Logo na primeira linha temos um console.log
que já entra em nossa pilha de chamadas, é executado e sai dela.
Depois disso, vamos para a linha debaixo, onde temos a função db.query, e é aqui que começa a diversão.
Essa função db.query é o que chamamos de função bloqueante, o que significa que ela leva um certo tempo para ser executada, pois nesse caso ela precisa se conectar ao banco de dados, executar uma consulta, aguardar a resposta do banco de dados, e trazer as informações de volta, e tudo isso leva um certo tempo, e se o Node não tomasse nenhuma ação em cima disso, essa função db.query ficaria presa em nossa call stack, bloqueando a execução do código, ou seja, não poderíamos passar para a próxima linha até que a função db.query fosse resolvida, e é por essa razão que chamamos ela de função bloqueante, porque ela bloqueia nossa pilha de processamento.
Porém, o Node.js não deixa isso acontecer, pois ele entende que essa função db.query vai bloquear a call stack, então ele pega e joga essa função para dentro da Thread Pool e a remove da nossa call stack.
Enquanto nossa query está sendo executada dentro da Thread Pool, nosso código continua sendo executado, então ele vai normalmente para nosso console.log()
da última linha, executa e o remove logo em seguida, e a execução do código principal termina.
Então lá na Thread Pool, a query está sendo executada e após terminar a execução da mesma, a função de callback do db.query é lançada na “Callback Queue”.
Após a função de callback da db.query estar dentro da callback queue, o Event Loop entra em ação, pense nele como um while(true) que fica sempre verificando se existe alguma função pronta para ser executada dentro da callback queue.
Se o Event loop encontrar alguma função que está pronta para ser executada e nossa pilha de chamadas não tiver mais funções pendentes para serem executadas, ele puxa essa função que está pronta para ser executada para a pilha de chamadas e continua processando o código normalmente.
Lembra que dentro da função de callback da query.db tínhamos um console.log
? Então, em seguida, ele será executado e após sua execução será removido da pilha. E agora, como nossa função de callback não tem mais nada para executar, ela também é retirada da pilha.
E é assim que o Node.js consegue executar código assíncrono.
Bom, isso é tudo que eu pretendia escrever para esse post, talvez em um próximo post, eu escreva mais do funcionamento do Node e etc.
Espero que tenha lhe ajudado de alguma forma, seu feedback (positivo ou negativo) é sempre bem-vindo :D
Top comments (2)
Cara que didático, foi muito simples de entender!
Massa demais! Ótimo Post