Node.js este un mediu de execuție JavaScript construit pe motorul V8, care permite rularea codului din afara browserului. În practică, acest lucru este crucial deoarece transformă JavaScript dintr-un limbament exclusiv frontend într-o unealtă versatilă pentru backend, CLI tools și microservicii. Înțelegerea modului în care V8 compilează codul și cum libuv gestionează operațiile asincrone este esențială pentru a scrie aplicații scalabile și eficiente, evitând blocajele comune ale programării concurente.
Concepte cheie
- V8 Engine: Este motorul open-source dezvoltat de Google care compila codul JavaScript direct în mașinărie nativă, oferind performanță extrem de ridicată comparativ cu interpretarea clasică.
- Single-threaded Event Loop: Node.js rulează pe un singur fir de execuție principal, gestionând mii de conexiuni simultane prin mecanismul non-blocking, fără a aștepta finalizarea operațiilor I/O.
- libuv: Este biblioteca cross-platform care abstractizează sistemul de operare, oferind implementarea reală a event loop-ului și gestionând thread pool-ul pentru operații grele.
- Non-blocking I/O: Operațiile precum citirea fișierelor sau cererile HTTP nu blochează firul principal, ci sunt delegate sistemului de operare, permițând continuarea procesării altor task-uri.
- Callback Queue & Microtask Queue: Mecanisme distincte care ordonează execuția funcțiilor de revenire, asigurând că promisiunile (microtasks) au prioritate față de callback-urile standard (macrotasks).
- Thread Pool: Un set limitat de fire de lucru (default 4) folosit de libuv pentru operații I/O care nu pot fi efectuate non-blocking, cum ar fi criptarea hash-urilor sau rezolvarea DNS.
Exemplu practic
Să presupunem că ai o funcție care citește un fișier mare. Dacă folosești fs.readFileSync, firul principal se blochează până la finalizare, oprind serverul să răspundă la alte cereri. În schimb, fs.readFile sau varianta promisă fs.promises.readFile returnează controlul imediat. Evenimentul este pus în coadă, iar când sistemul de operare notifică finalizarea, event loop-ul apelează callback-ul. Acest model permite serverului tău să servească sute de utilizatori simultan, chiar dacă fiecare cerere implică o operație lentă de disc.
Greșeli frecvente
- Executarea de cod CPU-intensiv pe firul principal: Calculul matematic complex sau procesarea imaginilor în main thread va bloca event loop-ul, făcând aplicația să pară „înghețată” pentru toți utilizatorii.
- Ignorarea erorilor în callback-uri: Uitatarea parametrului de eroare (
err) în funcțiile asincrone duce la crash-uri neașteptate sau la pierderea datelor critice. - Abuzul thread pool-ului: Folosirea excesivă a operațiilor care recurg la thread pool (ex:
crypto.pbkdf2) poate epuiza firele disponibile, creând gâtuiri (bottlenecks) majore.
Întrebări de interviu
De ce Node.js este considerat non-blocking? Pentru că operațiile de intrare/ieșire sunt delegate sistemului de operare sau unui thread pool extern, iar firul principal continuă să execute alte instrucțiuni până când rezultatul este pregătit.
Care este diferența dintre process.nextTick() și setImmediate()?
process.nextTick() execută callback-ul imediat după ce operația curentă se termină, înainte ca event loop-ul să treacă la următorul pas, în timp ce setImmediate() execută callback-ul în faza de check a event loop-ului.
Exercițiu
Creează un script Node.js simplu care simulează o sarcină CPU-intensivă (ex: un buclă for care calculează numere prime) și o operație I/O (ex: citirea unui fișier mic). Măsoară timpul de execuție al ambelor. Observă cum bucla CPU blochează tot procesul, în timp ce I/O-ul este non-blocking. Încearcă apoi să muți calculul CPU într-un Worker Thread (dacă ești pe versiunea 10.5+) și observă diferența de responsivitate a aplicației principale.