Node.js é um interpretador de JavaScript que funciona do lado do servidor criado em cima do V8 que é o motor de JavaScript da Google e que roda no seu Chrome, além disso ele conta com outras bibliotecas que o auxiliam no gerenciamento dos processor, como por exemplo a Libuv que falaremos mais adiante.
O Node.js age como uma ponte entre uma API acessável via JavaScript e funções em C++ do V8, foi criado por Ryan Dahl em 2009.
Conta-se que Ryan se inspirou depois de ver barra de progresso de upload de arquivos no Flickr, percebeu que o navegador não sabia o quanto do arquivo foi carregado e tinha que consultar o servidor web. Loco não?
O Node.js pode ser considerado uma plataforma de execução de aplicações em JavaScript no lado do servidor, como visto na imagem abaixo.
Então o que é o tal do V8 que é a base fundamental do Node.js?
Ele é nada menos que o interpretador de JavaScript, tipo uma máquina virtual, desenvolvido pelo Google e usado no Chrome. Feito em C++ e open-source.
O trabalho dele é basicamente compilar o código de JavaScript para o código nativo de máquina para depois executá-lo. Ele levou a velocidade dos códigos compilados para o JavaScript.
O Node.js trabalha apenas com uma thread, podendo ser criadas outras, com isso economizando muita memória, diferentemente da forma que o Apache trabalha e você percebe claramente a diferença de utilização de memória, já que com apenas uma thread você não precisa criar um processo novo para cada usuário conectado, acarretando também em uma economia de CPU.
Mas como ele consegue gerenciar a porra toda apenas com uma thread?
Com uma coisinha linda fachamada Event Loop.
O Event Loop nada mais é que uma fila infinita que recebe todos os eventos emitidos pelo Node.js, isso inclui as requisições que recebemos no servidor HTTP.
Quando o evento chega para ser exeutado no Event Loop, caso ele seja assíncrono, ele será enviado para onde deve ser executado, por exemplo: filesystem, network, process, etc.
Como o processo é assíncrono ele irá executar e só após sua finalização que ele dispara o trigger para seu callback, esse voltando para a fila que irá ser executada pelo Event Loop.
Libuv é uma biblioteca multi-plataforma que fornece notificação de eventos de forma assíncrona, isso inclui nosso sagrado I/O, foi originalmente desenvolvida para o Node.js, sendo a maior motivação a integração com o Windows.
Essa biblioteca veio para fazer o trabalho da libev e libeio agregando também a parte de DNS do C-Ares.
Onde a libev
gerenciava o Event Loop e a libeio
gerenciava o I/O assíncrono.
Foi no Node 0.5 que ela entrou em cena e na versão 0.9 a libev
foi removida.
- Full-featured event loop backed by epoll, kqueue, IOCP, event ports.
- Asynchronous TCP and UDP sockets
- Asynchronous DNS resolution
- Asynchronous file and file system operations
- File system events
- ANSI escape code controlled TTY
- IPC with socket sharing, using Unix domain sockets or named pipes (Windows)
- Child processes
- Thread pool
- Signal handling
- High resolution clock
- Threading and synchronization primitives
Lista retirada da documentação
Caso você queira se aprofundar mais indico esse material.
Qualquer função do Node.js, por padrão, é assíncrona por isso sempre precisamos de uma função que executará após o final desse processamento, essa que executa posteriormente é chamada de callback, falaremos muito mais sobre isso futuramente.
Mas então o que quer dizer que o I/O é assíncrono?
Basicamente diz que qualquer leitura ou escrita de dados não espera seu processo finalizar para continuar o script, nesse caso os processos ocorrem paralelamente à execução.
Para termos uma ideia melhor de como é o funcionamento assíncrono, vamos pensar um restaurante sendo síncrono.
No restaurante síncrono quando uma mesa é atendida ela precisa receber seu pedido antes que o garçom possa antender outra mesa!!!
Agora no restaurante assíncrono o mesmo garçom pode atender vários pedidos e enviá-los para a cozinha. Será a cozinha a responsável por responder cada pedido na ordem que para eles forem mais importantes ou mais rápidos. Nesse caso a ordem da resposta dos pedidos pode ser diferente da ordem pedida para a cozinha.
Quando um pedido é finalizado no Restaurante Assíncrono uma campainha/evento é emitido.
Agora no Restaurante Assíncrono o garçom pode atender todas as mesas que existirem apenas enviando seus pedidos para serem executados na cozinha.
O mesmo acontece com nossos sistemas, quando você envia uma requisição assíncrona você não tem a certeza quando ela irá retornar, por isso usamos Promises, mas isso é um assunto posterior.
Isso me lembrou o Princípio da incerteza de Heisenberg na física, mais alguém pira nisso como eu? :p
O node.js trabalha uma tread-pool a partir de uma thread, a thread em que ele é instanciado, é facilmente confundível com o que diz respeito a multi-thread, porém o node não dispõe desse custoso mecanismo para fazer i/o assíncrono, se você quiser dispor de todos os mecanismos de núcleos de um processador, você pode usar mecanismo de balanceamento e comunicação entre portas, cluster(um módulo nativo do node.js).
Definição: Uma thread-pool pode ser comparada com um array em que o número de colunas representaria uma idle-thread, uma thread pré executada que só espera um processo para trabalhar e o processo, o indice desse array. O node.js trabalha o processo como um I/O que a aplicação em node.js faz. Ou seja um I/O para uma idle-thread.
A api do node.js consiste de uma forte influência de outras plataformas.
O unix, sistema operacional usado como base para osx e linux, é uma forte influência para o node.js, não é atoa que roda muito bem no unix, discartando o mérito do nosso amiguinho ruindows* do tio Gates.
Por causa dessa influência o node.js é extensivamente modular, possuindo módulos para tudo, inclusive a sua aplicação será tratada como módulo para o node.js. Então é muito importante ter um código consistente, pouco dependente, pouco aclopado, e por fim, modularizado :)