Node.js e o Event Loop
Conhecer como o Node.js e o Event Loop funcionam é crucial para ser mais acertivo no desenvolvimento de soluções a fim de ter softwares com um comportamento mais preditivo.
Camadas do Node.js
Podemos considerar as camadas do Node.js da seguinte forma:

- Core js API's: são as api's do Node.js;
- v8: interpreta (roda) o código javascript fora do navegador;
- binding: encapsula e expõe funções de baixo nível para o javascript;
- libuv: engine de I/O de baixo nível.
Assim, o fluxo de execução seria assim:

┌──────────────────────────┐
│ Sua aplicação JS │ ← código que você escreve (app.js)
└─────────────┬────────────┘
│
▼
┌──────────────────────────┐
│ Node.js APIs │ ← fs, http, crypto, stream, etc.
│ (módulos nativos JS + C++)│
└─────────────┬────────────┘
│
▼
┌──────────────────┐
│ Bindings C++ │ ← “cola” entre JS e código nativo
└─────────┬────────┘
│
┌───────┴──────────┐
│ │
┌────▼─────┐ ┌─────▼─────┐
│ V8 JS │ │ libuv │
│ Engine │ │ event loop│
└────┬─────┘ └─────┬─────┘
│ │
▼ ▼
Compila/ I/O assíncrono
executa JS (rede, disco, timers)
│ │
└──────────┬───────┘
▼
┌─────────────────┐
│ SO (Linux, │
│ Windows, macOS) │
└─────────────────┘
Event Loop
O Event Loop pode ser apresentado da seguinte forma:

- task queue: callback de api's como setTimeout, setImediate;
- microtask queue: callback de promise, como then(), catch(), finally(), await ();
- nextTick queue: callback de nextTick
Detalhes do Event Loop:

- Todo fluxo de execução síncrono é executado no call stack;
- a ordem de prioridade de execução é:
- código síncrono na call stack;
- nextTick queue;
- microtask queue;
- task queue
Em detalhes:
- executa o que tem em
call stack; call stackestá vazia?- executa o que tem em
nextTick queue; nextTick queueestá vazia?- executa o que tem em
microtask queue; microtask queueestá vazia?- executa o que tem em
task queue.
Observação: caso a nextTick queue, microtask queue e task queue adicione callbacks em queue de maior prioridade da execução corrente, o callback de maior prioridade será executado e só depois a execução das outras queues de menor prioridade seguirão.
Assim:
- Se dentro de uma task (ex: setTimeout) você enfileirar algo em process.nextTick, o nextTick será executado antes de continuar processando outras microtasks e tasks.
- Dentro de uma microtask (Promise), se você adicionar um process.nextTick, ele vai ser executado imediatamente antes de continuar a microtask queue.
- Ou seja: ao inserir em uma fila de maior prioridade, ela será “promovida” e executada antes de continuar o esvaziamento das filas menos prioritárias.
process.nextTick queue
↓
microtask queue (Promises, queueMicrotask)
↓
event loop tasks (timers, I/O, setImmediate)
O vídeo abaixo ajuda de forma animada a entender o fluxo de execução:
Código Javascript
Abaixo exemplos de código js que apresenta o fluxo de execução no Event Loop:
(async () => {
console.log('start');
setTimeout(() => console.log('timeout'), 0)
Promise.resolve().then(() => console.log('promise'))
process.nextTick(() => console.log('nextTick'))
console.log('end');
})();
// resultado: begin -> end -> nextTick -> promise -> timeout
async function first() {
console.log('first');
}
async function second() {
console.log('second');
}
(async () => {
console.log('begin!');
process.nextTick(() => console.log('nextTick'));
await first();
// callback nextTick executa aqui
await second();
console.log('end!');
})();
// resultado: begin -> first -> nextTick -> second -> end
async function first() {
console.log('first');
}
async function second() {
console.log('second');
}
(async () => {
console.log('begin!');
await first();
await second();
process.nextTick(() => console.log('nextTick'));
console.log('end!');
})();
// resultado: begin -> first -> second -> end -> nextTick
