Pular para o conteúdo principal

Node.js e o Event Loop

· 3 min para ler
Leandro Andrade
Leandro Andrade
Software Developer

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:

nodejs-estrutura.png

  • 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:

fluxo-execucao-nodejs.png

┌──────────────────────────┐
│ 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:

event-loop-1.png

  • 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:

event-loop-2.png

  • Todo fluxo de execução síncrono é executado no call stack;
  • a ordem de prioridade de execução é:
    1. código síncrono na call stack;
    2. nextTick queue;
    3. microtask queue;
    4. task queue

Em detalhes:

  • executa o que tem em call stack;
  • call stack está vazia?
  • executa o que tem em nextTick queue;
  • nextTick queue está vazia?
  • executa o que tem em microtask queue;
  • microtask queue está 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