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.
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.
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.
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.
# 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));
});
[ ] 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