Node.js — Cheat Sheet

Tag: nodejs, javascript, backend, runtime, event-loop, asynchronous, streams

Livello: intermedio

TL;DR: Runtime JavaScript server-side basato su V8, single-threaded ed event-driven con I/O non bloccante. Gestione asincrona tramite Event Loop e libuv.

Concetti chiave

Node.js esegue codice JavaScript fuori dal browser tramite il motore V8 di Google.

Architettura single-threaded sul thread principale con delega I/O non bloccante a libuv.

Il pool di thread di libuv gestisce operazioni bloccanti del sistema operativo (filesystem, DNS, crypto).

Event Loop processa fasi cicliche: timers, pending callbacks, idle/prepare, poll, check, close callbacks.

Microtask queue (process.nextTick, Promise resolve) ha priorita' assoluta su ogni tick dell'Event Loop.

CommonJS usa require() e module.exports sincroni; ES Modules usa import ed export statici.

Modello Streams (Readable, Writable, Duplex, Transform) per elaborazione dati a pezzi senza saturare memoria.

EventEmitter implementa il pattern Observer per eventi personalizzati con on() ed emit().

Cluster module e worker_threads abilitano concorrenza e sfruttamento di CPU multi-core.

Buffer gestisce dati binari grezzi allocati fuori dalla memoria gestita dal garbage collector di V8.

Domande tipiche e risposte pronte

Q: Come gestisce la concorrenza Node.js se il thread principale e' singolo?R: Sfrutto l'I/O asincrono delegato al kernel del sistema operativo o al thread pool di libuv; il thread principale esegue solo i callback quando pronti.

Q: Qual e' la differenza tra process.nextTick() e setImmediate()?R: process.nextTick si svuota immediatamente al termine della fase corrente prima dell'Event Loop, mentre setImmediate viene eseguito nella fase di check successiva.

Q: Come eviti che un calcolo pesante blocchi l'applicazione?R: Sposto i task CPU-intensive su un pool di worker_threads, divido il lavoro con micro-batch asincroni o delego a un microservizio dedicato.

Q: Come gestisci il memory leak in Node.js?R: Monitoro heap con snapshot V8, verifico listener EventEmitter non rimossi, chiudo timer aperti ed evito riferimenti globali o closure trattenute.

Insidie e errori comuni

Blocco dell'Event Loop tramite parsing JSON gigante sincrono o operazioni regex complesse con backtracking catastrofico.

Mancata gestione degli errori negli stream senza pipe sicura o pipeline utility, causando memory leak.

unhandledRejection o uncaughtException non intercettati che terminano il processo senza cleanup pulito.

Confusione tra fs.promises e metodi sincroni bloccanti come fs.readFileSync su route HTTP ad alto traffico.

Mancata rimozione dei listener con removeListener o superamento del limite predefinito setMaxListeners.

Snippet e comandi

# Avvio script e ispezione memoria/debug
node --inspect index.js
node --max-old-space-size=4096 index.js

JavaScript

// Gestione sicura di stream tramite pipeline
import { pipeline } from 'node:stream/promises';
import { createReadStream, createWriteStream } from 'node:fs';
import { createGzip } from 'node:zlib';

async function compressFile(source, destination) {
  await pipeline(
    createReadStream(source),
    createGzip(),
    createWriteStream(destination)
  );
}

JavaScript

// Servizio HTTP minimale nativo con graceful shutdown
import http from 'node:http';

const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify({ status: 'ok' }));
});

server.listen(3000);

process.on('SIGTERM', () => {
  server.close(() => process.exit(0));
});

Da ripassare

[ ] Ordine esatto di esecuzione delle fasi dell'Event Loop di libuv

[ ] Differenze architetturali tra worker_threads e modulo cluster

[ ] Utilizzo di AbortController nelle API asincrone native di Node.js

[ ] Strategie di backpressure nella gestione di flussi con Streams API

Jarvis Interviews
Jarvis