A la lliçó anterior vas descobrir que libuv manté un cicle que pregunta sense descans «hi ha res acabat?, hi ha cap callback per executar?». Aquest cicle és el bucle d'esdeveniments, i és la peça que converteix Node.js en el que és. Tota la resta —els callbacks, les promeses, els esdeveniments, el servidor HTTP, la base de dades— està construïda a sobre.
Aquesta és, sense exagerar, la lliçó més important del curs. No pas perquè hagis d'escriure codi que manipuli el bucle directament (gairebé mai no ho faràs), sinó perquè l'ordre en què s'executen les coses en Node només té sentit quan en coneixes les fases. Sense aquest coneixement, tard o d'hora et trobaràs mirant una sortida per consola que apareix «en l'ordre equivocat», un temporitzador que es dispara tard, o un servidor que respon bé al teu portàtil i malament en producció, i no tindràs cap model mental per explicar-ho.
En acabar podràs predir línia a línia la sortida de qualsevol programa asíncron en Node, sabràs quan setImmediate guanya a setTimeout i quan no, entendràs per què process.nextTick es cola davant de tothom i per què això pot ser perillós, i sabràs mesurar la mètrica de salut número u d'un servidor Node: el retard del bucle d'esdeveniments.
Contingut
- Què és exactament el bucle d'esdeveniments
- Les sis fases, en ordre
- Fase 1: timers
- Fase 2: pending callbacks
- Fase 3: idle i prepare
- Fase 4: poll, el cor del cor
- Fase 5: check i
setImmediate - Fase 6: close callbacks
setTimeoutcontrasetImmediate- Les dues cues que es colen:
process.nextTicki les microtasques - Una traça completa que has de poder predir
- La inanició del bucle: el perill de
nextTick setTimeout(fn, 0)no és immediat- Mesurar el retard del bucle d'esdeveniments
- Escena Viva: per què un càlcul síncron degrada tothom
- Què és exactament el bucle d'esdeveniments
El bucle d'esdeveniments és un bucle while escrit en C dins de libuv. Res més místic que això. El seu cos, molt simplificat, s'assembla a això:
/* Pseudocodi d'uv_run(), a libuv */
while (hi_ha_tasques_pendents(loop)) {
executar_fase_timers(loop);
executar_fase_pending_callbacks(loop);
executar_fase_idle_prepare(loop);
executar_fase_poll(loop); /* aqui es on s'espera */
executar_fase_check(loop);
executar_fase_close_callbacks(loop);
}
/* En sortir del while, el proces acaba */Cada volta completa d'aquest while s'anomena tick o iteració del bucle. I la condició hi_ha_tasques_pendents és exactament el compte de referències que vas veure a la lliçó anterior: mentre hi hagi un temporitzador programat, un socket obert o una lectura de fitxer en curs, el bucle continua girant; quan el compte arriba a zero, el while acaba i el procés mor.
Tres idees que cal fixar abans de continuar:
- El bucle d'esdeveniments viu al fil principal, el mateix que executa el teu JavaScript. No són dues coses en paral·lel. Mentre el teu callback s'executa, el bucle està aturat esperant que li tornis el control. Aquesta és la raó mecànica per la qual bloquejar el fil principal ho bloqueja tot.
- Cada fase té la seva pròpia cua de callbacks. No hi ha una única «cua d'esdeveniments»; n'hi ha diverses, i la fase actual determina quina es buida.
- Dins d'una fase, els callbacks s'executen fins a exhaurir la seva cua (amb un límit de seguretat en algunes fases) i només llavors es passa a la següent.
- Les sis fases, en ordre
flowchart TD
START(["Arrencada:<br/>s'executa el teu script sencer"]) --> T
T["<b>1. timers</b><br/>callbacks de setTimeout<br/>i setInterval vencuts"]
P["<b>2. pending callbacks</b><br/>callbacks d'E/S ajornats<br/>de la iteracio anterior"]
I["<b>3. idle / prepare</b><br/>us intern de libuv"]
POLL["<b>4. poll</b><br/>recull esdeveniments d'E/S nova<br/>i executa els seus callbacks.<br/><i>Aqui es on s'espera</i>"]
CH["<b>5. check</b><br/>callbacks de setImmediate"]
CL["<b>6. close callbacks</b><br/>esdeveniments 'close' de sockets,<br/>servidors i streams"]
FIN{"Queden<br/>referencies?"}
T --> P --> I --> POLL --> CH --> CL --> FIN
FIN -->|"Si"| T
FIN -->|"No"| END(["El proces acaba"])
Entre cada parell de fases —i també entre cada callback individual, a Node 11 i posteriors— es buiden dues cues especials que no formen part del diagrama perquè no són fases del bucle: la de process.nextTick i la de les microtasques de promeses. Les veurem a l'apartat 10, i són la font número u de confusió.
Aquí tens la vista de conjunt abans d'entrar en el detall:
| # | Fase | Quins callbacks executa | La veus sovint? |
|---|---|---|---|
| 1 | timers | setTimeout i setInterval el termini dels quals ja ha vençut |
Constantment |
| 2 | pending callbacks | Callbacks d'operacions del sistema ajornats (p. ex. ECONNREFUSED de TCP) |
Poques vegades de manera conscient |
| 3 | idle, prepare | Ús intern de libuv | Mai des de JavaScript |
| 4 | poll | Callbacks d'E/S: fitxer llegit, petició HTTP rebuda, socket amb dades | És on viu un servidor |
| 5 | check | setImmediate |
Quan ho demanes explícitament |
| 6 | close callbacks | socket.on('close'), servidor.on('close') |
En tancar recursos |
- Fase 1: timers
En aquesta fase el bucle mira el seu munt de temporitzadors i executa els callbacks de tots aquells el termini dels quals ja ha vençut.
El matís essencial: setTimeout(fn, 100) no vol dir «executa fn d'aquí a exactament 100 ms». Vol dir «no executis fn abans que passin 100 ms». El llindar és un mínim, no una cita.
// src/laboratori/timers-precisio.js
// Un temporitzador es un minim, no una promesa de puntualitat.
const inici = Date.now();
setTimeout(() => {
console.log(`Temporitzador de 100 ms disparat als ${Date.now() - inici} ms`);
}, 100);
// Un bloqueig sincron de 300 ms just despres de programar-lo.
const limit = Date.now() + 300;
while (Date.now() < limit) {
// Treball sincron: el bucle d'esdeveniments ni tan sols ha comencat a girar.
}
console.log(`Bloqueig sincron acabat als ${Date.now() - inici} ms`);El temporitzador estava vençut des dels 100 ms, però el bucle no el va poder atendre fins que el fil principal va quedar lliure. Un temporitzador no es dispara mai abans del seu termini, però pot disparar-se moltíssim després. En un servidor saturat, aquesta diferència és el senyal que alguna cosa va malament.
Nota sobre setInterval: no garanteix una cadència exacta. Si el callback triga més que l'interval, Node no acumula execucions pendents: descarta les que no hi cabien i programa la següent. Un setInterval(fn, 100) el fn del qual triga 250 ms acaba corrent cada 250 ms, no pas cada 100.
- Fase 2: pending callbacks
Aquesta fase executa callbacks de certes operacions del sistema que van quedar ajornats de la iteració anterior. El cas típic és un error de TCP: quan intentes connectar a un port tancat, alguns sistemes notifiquen l'ECONNREFUSED de manera que libuv l'encua aquí en lloc de a poll.
És la fase que menys tocaràs directament. Has de saber que existeix —perquè explica algun ordre d'execució aparentment estrany amb errors de xarxa— però no hi programaràs res. També se la coneix a la documentació antiga com a pending i/o callbacks.
- Fase 3: idle i prepare
Ús intern de libuv, sense API pública des de JavaScript. Node les utilitza per a la seva pròpia preparació abans d'entrar a poll. Apareixen a tots els diagrames per completitud i perquè, quan llegeixis la documentació oficial, no et preguntis què t'has perdut. Les pots ignorar.
- Fase 4: poll, el cor del cor
Si hi ha una fase que has d'entendre de debò, és aquesta. Un servidor Node passa el 99 % de la seva vida aquí.
La fase poll fa dues coses:
- Calcular quant temps es pot permetre bloquejar esperant esdeveniments d'E/S.
- Processar els esdeveniments d'E/S que ja han arribat, executant-ne els callbacks.
I l'algorisme de decisió, que és el més interessant, és aquest:
flowchart TD
A["Entrem a la fase poll"] --> B{"Hi ha callbacks d'E/S<br/>a la cua?"}
B -->|"Si"| C["Executar-los fins a exhaurir la cua<br/>(o arribar al limit del sistema)"]
C --> Z["Passar a la fase check"]
B -->|"No"| D{"Hi ha setImmediate<br/>programats?"}
D -->|"Si"| Z2["Acabar poll JA<br/>i passar a check"]
D -->|"No"| E{"Hi ha temporitzadors<br/>a punt de vencer?"}
E -->|"Si"| F["BLOQUEJAR aqui esperant E/S,<br/>com a maxim fins que venci<br/>el temporitzador mes proper"]
E -->|"No"| G["BLOQUEJAR aqui esperant E/S<br/>indefinidament"]
F --> Z
G --> Z
Quatre conseqüències pràctiques d'aquest algorisme:
- «Bloquejar» aquí és el que vols, no pas un problema. Quan el teu servidor no té res a fer, es queda adormit a la crida
epoll_waitdel sistema operatiu, sense consumir CPU, fins que arriba un paquet de xarxa. Un servidor Node en repòs gasta 0 % de processador. És el mateix cambrer de la primera lliçó: no espera dret mirant la cuina, s'asseu fins que sona la campana. setImmediateté prioritat sobre esperar. Si hi ha unsetImmediatependent, poll no s'adorm: talla i passa a check. Això és el que fa quesetImmediatesigui fiable dins de callbacks d'E/S.- Els temporitzadors acoten l'espera. Si hi ha un
setTimeoutque venç d'aquí a 50 ms, poll dormirà com a màxim 50 ms, per poder tornar a la fase timers a temps. - La cua de poll té un límit. Node no processa infinits esdeveniments d'E/S seguits: hi ha un topall per no deixar sense atendre la resta de fases. El que sobra s'atén a la iteració següent.
- Fase 5: check i
setImmediate
setImmediateLa fase check existeix per un únic motiu: donar a setImmediate un lloc garantit just després de la fase poll.
El nom de setImmediate és enganyós: no vol dir «executa'l ja», vol dir «executa'l tan bon punt acabi de processar l'E/S d'aquesta iteració». És a dir: a la fase check de la iteració actual.
// src/laboratori/check.js
const fs = require('node:fs');
fs.readFile(__filename, () => {
// Som a la fase POLL (aquest es un callback d'E/S).
console.log('1. Callback de lectura de fitxer (fase poll)');
setImmediate(() => {
console.log('2. setImmediate (fase check, mateixa iteracio)');
});
setTimeout(() => {
console.log('3. setTimeout 0 (fase timers, iteracio seguent)');
}, 0);
});1. Callback de lectura de fitxer (fase poll) 2. setImmediate (fase check, mateixa iteracio) 3. setTimeout 0 (fase timers, iteracio seguent)
Aquest ordre és sempre el mateix, sense excepció. I ja saps per què: des de poll, la fase següent és check, mentre que per arribar a timers cal completar la iteració sencera i començar-ne una altra.
- Fase 6: close callbacks
Quan un recurs es tanca —un socket, un servidor, un stream—, el seu esdeveniment 'close' no s'emet immediatament: s'encua en aquesta última fase.
// src/laboratori/tancament.js
const net = require('node:net');
const servidor = net.createServer();
servidor.listen(0, () => { // El port 0 significa "un de lliure qualsevol"
console.log(`1. Servidor escoltant al port ${servidor.address().port}`);
servidor.close(); // Demanem el tancament...
console.log("2. close() ja ha tornat, pero l'esdeveniment encara no s'ha emes");
setImmediate(() => console.log('3. setImmediate (fase check)'));
});
servidor.on('close', () => {
console.log('4. Esdeveniment close (fase close callbacks)');
});1. Servidor escoltant al port 43217 2. close() ja ha tornat, pero l'esdeveniment encara no s'ha emes 3. setImmediate (fase check) 4. Esdeveniment close (fase close callbacks)
Fixa't en la lliçó de fons, que es repetirà en tota la teva carrera amb Node: demanar que una cosa es tanqui i que la cosa estigui tancada són dos moments diferents. Al Mòdul 11 això serà important per apagar el servidor d'Escena Viva de manera ordenada sense tallar peticions en curs.
setTimeout contra setImmediate
setTimeout contra setImmediateAra la pregunta clàssica de les entrevistes, que té una resposta amb dues parts.
9.1 Al nivell superior de l'script: no determinista
// src/laboratori/ordre-no-determinista.js
setTimeout(() => console.log('setTimeout 0'), 0);
setImmediate(() => console.log('setImmediate'));Executa això cinc o sis vegades seguides:
Veuràs que l'ordre canvia entre execucions:
L'explicació: setTimeout(fn, 0) es converteix internament en un termini d'1 ms. Quan el bucle arrenca per primer cop i entra a la fase timers, comprova si ha passat aquest mil·lisegon. Si l'arrencada del procés (carregar mòduls, inicialitzar V8) va trigar més d'1 ms, el temporitzador ja està vençut i s'executa primer; si va trigar menys, no està vençut, el bucle continua fins a check i guanya setImmediate. Com que aquest temps d'arrencada depèn de la càrrega de la màquina, el resultat és una cursa.
Regla pràctica: no escriguis mai codi la correcció del qual depengui de l'ordre entre
setTimeoutisetImmediateal nivell superior. Si t'importa l'ordre, és que estàs fent servir l'eina equivocada.
9.2 Dins d'un callback d'E/S: totalment determinista
Com vas veure a l'apartat 7, dins d'un callback d'E/S ja som a la fase poll, i des d'allà check ve immediatament després, mentre que timers queda a una iteració de distància. setImmediate guanya sempre. Sense excepcions, sense dependre de la màquina.
| Context | Guanyador | Per què |
|---|---|---|
| Nivell superior de l'script | Impredictible | Depèn de si ha passat 1 ms des de l'arrencada |
Dins d'un callback d'E/S (fs, net, http) |
setImmediate, sempre |
Som a poll; check és la fase següent |
Dins d'un setImmediate |
setTimeout, normalment |
Ja hem passat check: el següent setImmediate va a la propera iteració |
Dins d'un setTimeout |
Impredictible | La mateixa cursa del primer cas |
9.3 Quan fer servir cadascun
| Vull… | Faig servir |
|---|---|
| Cedir el control ara mateix per no bloquejar, i continuar com abans millor | setImmediate(fn) |
| Executar una cosa d'aquí a N mil·lisegons | setTimeout(fn, N) |
| Trossejar una feina llarga en bocins que no bloquegin | setImmediate entre bocí i bocí |
| Executar una cosa abans que el bucle continuï, costi el que costi | process.nextTick (amb compte, vegeu l'apartat següent) |
- Les dues cues que es colen:
process.nextTick i les microtasques
process.nextTick i les microtasquesAquí hi ha el 80 % de la confusió que pateix la gent amb Node. Existeixen dues cues que no pertanyen a cap fase i que es buiden entre fases, i també entre cada callback individual:
| Cua | S'omple amb | Prioritat |
|---|---|---|
| nextTick queue | process.nextTick(fn) |
La més alta. Es buida primer |
| microtask queue | Promise.then/catch/finally, await, queueMicrotask |
La segona. Es buida després de la de nextTick |
I la regla completa, en l'ordre exacte en què Node actua:
Després d'acabar cada callback, i abans de continuar amb el següent o de canviar de fase, Node:
- Buida completament la cua de
nextTick(inclosos elsnextTickque s'afegeixin mentre la buida).- Buida completament la cua de microtasques (incloses les que s'afegeixin mentre la buida).
Nota històrica important: aquest comportament va canviar a Node.js 11. Abans, les cues es buidaven només entre fases, no entre callbacks individuals. Si trobes articles antics amb sortides diferents, és per això. Tot el d'aquesta lliçó es refereix a Node 11 i posteriors, que és tot el que es fa servir avui.
Un exemple mínim que ho demostra:
// src/laboratori/cues.js
console.log('1. sincron');
setTimeout(() => console.log('5. setTimeout'), 0);
setImmediate(() => console.log('6. setImmediate'));
Promise.resolve().then(() => console.log('4. microtasca de promesa'));
process.nextTick(() => console.log('3. nextTick'));
console.log('2. sincron fi');(Les dues últimes línies es poden intercanviar, pel que ja saps de l'apartat 9.1. Les quatre primeres són inamovibles.)
I la demostració que les cues es buiden entre cada callback, no només entre fases:
// src/laboratori/cues-entre-callbacks.js
setTimeout(() => {
console.log('timer 1');
process.nextTick(() => console.log(' nextTick des del timer 1'));
}, 0);
setTimeout(() => {
console.log('timer 2');
process.nextTick(() => console.log(' nextTick des del timer 2'));
}, 0);Els dos temporitzadors són a la mateixa fase. Si les cues es buidessin només en canviar de fase, veuríem timer 1, timer 2 i després els dos nextTick. Que no sigui així confirma la regla: es buiden entre callback i callback.
Quan fer servir process.nextTick?
Gairebé mai, i per això mateix convé saber-ne els dos casos legítims:
- Garantir que un callback sigui sempre asíncron, fins i tot en la ruta d'error immediat. Ho veurem amb detall a la propera lliçó, Callbacks i Programació Asíncrona, perquè n'és el remei canònic.
- Permetre que qui et crida registri oients abans d'emetre un esdeveniment. És el que fa internament Node en diversos llocs: si un constructor emetés un esdeveniment de manera síncrona, ningú no hauria tingut ocasió de subscriure-s'hi encara.
// Patro 2: donar temps a registrar l'oient.
const EventEmitter = require('node:events');
class CarregadorCataleg extends EventEmitter {
constructor() {
super();
// MALAMENT: ningu no ha pogut subscriure-s'hi encara.
// this.emit('llest');
// BE: s'emet despres que el codi que ens ha creat pugui fer .on()
process.nextTick(() => this.emit('llest'));
}
}
const carregador = new CarregadorCataleg();
carregador.on('llest', () => console.log('Cataleg llest')); // Si que funcionaEn cas de dubte, fes servir setImmediate en lloc de nextTick. És més segur, perquè respecta el cicle del bucle en comptes de saltar-se'l. La raó, a l'apartat següent.
- Una traça completa que has de poder predir
Aquest és l'examen de la lliçó. Llegeix el programa sencer, escriu en un paper l'ordre de sortida que esperes i només després executa'l.
// src/laboratori/traca.js
// Repte: predir l'ordre de les 8 lletres abans d'executar.
console.log('A - sincron');
setTimeout(() => {
console.log('B - primer setTimeout');
process.nextTick(() => console.log('C - nextTick dins del primer setTimeout'));
Promise.resolve().then(() => console.log('D - promesa dins del primer setTimeout'));
}, 0);
setTimeout(() => {
console.log('E - segon setTimeout');
}, 0);
process.nextTick(() => console.log('F - nextTick del nivell superior'));
Promise.resolve().then(() => console.log('G - promesa del nivell superior'));
console.log('H - sincron fi');La resposta i el seu raonament:
A - sincron H - sincron fi F - nextTick del nivell superior G - promesa del nivell superior B - primer setTimeout C - nextTick dins del primer setTimeout D - promesa dins del primer setTimeout E - segon setTimeout
Pas a pas:
| Pas | Què passa | Sortida |
|---|---|---|
| 1 | S'executa l'script de dalt a baix. Els setTimeout, el nextTick i el .then només encuen, no executen |
A, H |
| 2 | Acaba l'script. Abans que el bucle comenci a girar, Node buida la cua de nextTick |
F |
| 3 | Després, la cua de microtasques | G |
| 4 | Primera iteració, fase timers. S'executa el primer callback vençut | B |
| 5 | Aquest callback ha acabat: es buida la cua de nextTick abans de continuar |
C |
| 6 | I tot seguit la de microtasques | D |
| 7 | Només ara es passa al segon callback de temporitzador, que era a la mateixa fase | E |
| 8 | Sense més cues ni referències, el bucle surt i el procés acaba | — |
Si has encertat l'ordre complet, entens el bucle d'esdeveniments. Si has fallat en C i D col·locant-los darrere d'E, tens al cap el model anterior a Node 11: les cues es buiden entre callbacks, no només entre fases.
- La inanició del bucle: el perill de
nextTick
nextTickLa cua de nextTick es buida completament, inclosos els nextTick que s'encuïn mentre s'està buidant. Això obre la porta a una fallada especialment desagradable:
// src/laboratori/inanicio.js
// AVIS: aquest programa NO arriba MAI al setTimeout. Atura'l amb Ctrl+C.
let voltes = 0;
function recursiuAmbNextTick() {
voltes++;
if (voltes % 1000000 === 0) {
console.log(`${voltes} voltes i el bucle continua sense avancar`);
}
process.nextTick(recursiuAmbNextTick); // Es torna a encuar a si mateix
}
setTimeout(() => {
console.log("Aixo NO s'imprimeix mai");
}, 100);
recursiuAmbNextTick();El bucle d'esdeveniments no arriba mai a la fase timers, perquè abans d'avançar ha de buidar la cua de nextTick, i aquesta cua es reomple sola indefinidament. El procés consumeix el 100 % d'un nucli, no atén res i no dona cap error. A això se'n diu inanició del bucle d'esdeveniments (event loop starvation), i és una fallada difícil de diagnosticar perquè el procés sembla viu.
Ara la mateixa estructura amb setImmediate:
// src/laboratori/sense-inanicio.js
let voltes = 0;
function recursiuAmbImmediate() {
voltes++;
if (voltes >= 1000000) return;
setImmediate(recursiuAmbImmediate);
}
setTimeout(() => {
console.log(`El setTimeout SI que s'executa, despres de ${voltes} voltes`);
}, 100);
recursiuAmbImmediate();La diferència és que un setImmediate encuat durant la fase check s'executa a la iteració següent, no pas a l'actual. Això deixa passar la resta de fases i el temporitzador arriba a disparar-se.
process.nextTick |
setImmediate |
|
|---|---|---|
| Quan s'executa | Abans de continuar, saltant-se el bucle | A la fase check d'una iteració del bucle |
| Recursió | Provoca inanició | Segura: cedeix el control cada volta |
| Prioritat | Màxima | Normal |
| Ús recomanat | Casos molt concrets (vegeu 10) | L'opció per defecte per a «més tard, però aviat» |
Aquesta és la regla que t'ha de quedar gravada: si necessites ajornar feina, fes servir setImmediate. process.nextTick és una eina esmolada per a dos casos específics.
setTimeout(fn, 0) no és immediat
setTimeout(fn, 0) no és immediatJa ho has vist de passada, però mereix el seu propi apartat perquè genera errors reals.
Quan escrius setTimeout(fn, 0), Node no programa 0 mil·lisegons. Internament aplica aquesta correcció:
// Comportament intern, simplificat
if (!(termini >= 1 && termini <= 2147483647)) {
termini = 1; // Qualsevol valor fora del rang es converteix en 1 ms
}És a dir: el mínim real és 1 mil·lisegon. I hi ha dos límits més que convé conèixer:
| Valor que escrius | El que passa realment |
|---|---|
setTimeout(fn, 0) |
Termini d'1 ms |
setTimeout(fn, -5) |
Termini d'1 ms |
setTimeout(fn) (sense termini) |
Termini d'1 ms |
setTimeout(fn, 2147483647) |
~24,8 dies. El màxim (enter de 32 bits amb signe) |
setTimeout(fn, 2147483648) |
Desbordament: es converteix en 1 ms i Node avisa per consola. S'executa immediatament! |
Aquest últim cas és un error real i recurrent: algú programa «un recordatori d'aquí a 30 dies» amb setTimeout(fn, 30 * 24 * 60 * 60 * 1000) i el callback es dispara a l'instant, perquè 2.592.000.000 supera el màxim. A Escena Viva, un recordatori de «el teu esdeveniment és demà» no s'ha d'implementar mai amb un setTimeout llarg: s'implementa amb una tasca programada externa o una cua de treballs.
I una altra conseqüència pràctica: el termini mínim real d'1 ms significa que 1000 setTimeout(fn, 0) encadenats triguen com a mínim un segon. Per trossejar feina sense esperar res, setImmediate és l'eina correcta i és molt més ràpida.
- Mesurar el retard del bucle d'esdeveniments
Arribem a la part més aplicable de la lliçó. El retard del bucle d'esdeveniments (event loop lag o delay) és la diferència entre el moment en què un callback hauria d'haver-se executat i el moment en què realment es va executar.
És la mètrica de salut número u d'un servidor Node, per damunt fins i tot de la CPU i la memòria, i la raó és directa: el retard del bucle és, literalment, el temps que els teus usuaris estan esperant perquè el procés està ocupat.
14.1 La mesura manual
// src/utils/mesurar-bucle.js
// Mesura el retard del bucle d'esdeveniments amb un temporitzador de referencia.
const INTERVAL_MS = 100;
function iniciarMesura({ intervalMs = INTERVAL_MS, llindarMs = 50 } = {}) {
// hrtime.bigint() dona nanosegons i no es veu afectat per canvis d'hora.
let ultimaMarca = process.hrtime.bigint();
const temporitzador = setInterval(() => {
const ara = process.hrtime.bigint();
// Temps real transcorregut, en mil·lisegons.
const transcorregutMs = Number(ara - ultimaMarca) / 1e6;
ultimaMarca = ara;
// El retard es l'exces sobre l'interval previst.
const retardMs = Math.max(0, transcorregutMs - intervalMs);
if (retardMs > llindarMs) {
console.error(`[bucle] retard de ${retardMs.toFixed(1)} ms`);
}
}, intervalMs);
// Que la mesura no impedeixi que el proces acabi.
temporitzador.unref();
return () => clearInterval(temporitzador);
}
module.exports = { iniciarMesura };Aquest fitxer ja és, tècnicament, un mòdul amb module.exports. A la lliçó Mòduls CommonJS i require() formalitzarem què significa exactament aquesta línia; de moment accepta que exposa la funció perquè altres fitxers la facin servir.
14.2 La mesura precisa: perf_hooks
Node porta una eina específica i molt més exacta, perquè mesura dins de libuv en lloc de fer-ho amb un temporitzador de JavaScript:
// src/laboratori/monitor-bucle.js
const { monitorEventLoopDelay } = require('node:perf_hooks');
// resolution: cada quants ms pren una mostra.
const histograma = monitorEventLoopDelay({ resolution: 10 });
histograma.enable();
// Simulem carrega: un bloqueig de 200 ms a mitja execucio del proces.
setTimeout(() => {
const limit = Date.now() + 200;
while (Date.now() < limit) { /* bloqueig deliberat */ }
}, 300);
setTimeout(() => {
histograma.disable();
// Els valors venen en nanosegons: dividim entre 1e6 per tenir ms.
const aMs = (n) => (n / 1e6).toFixed(2);
console.log("Retard del bucle d'esdeveniments:");
console.log(` minim : ${aMs(histograma.min)} ms`);
console.log(` mitjana : ${aMs(histograma.mean)} ms`);
console.log(` maxim : ${aMs(histograma.max)} ms`);
console.log(` percentil 50: ${aMs(histograma.percentile(50))} ms`);
console.log(` percentil 99: ${aMs(histograma.percentile(99))} ms`);
}, 1000);Retard del bucle d'esdeveniments: minim : 9.99 ms mitjana : 15.42 ms maxim : 208.67 ms percentil 50: 10.21 ms percentil 99: 208.67 ms
Com llegir aquests números:
| Percentil 99 del retard | Diagnòstic |
|---|---|
| < 10 ms | Sa. El procés respon amb marge |
| 10 – 50 ms | Acceptable, però hi ha alguna cosa que ocupa el fil. Val la pena investigar-ho |
| 50 – 200 ms | Dolent. Els usuaris ho noten a cada petició |
| > 200 ms | Crític. Hi ha feina síncrona pesada. El servidor està funcionalment caigut a estones |
Fixa't en una cosa important de l'exemple: la mitjana és de 15 ms, un número tranquil·litzador. El percentil 99 és de 208 ms, un desastre. Per això en producció es vigilen percentils i no pas mitjanes: la mitjana amaga exactament els problemes que importen. Al Mòdul 11 publicarem aquesta mètrica de manera contínua per al servidor d'Escena Viva.
- Escena Viva: per què un càlcul síncron degrada tothom
Aterrem tot l'anterior al projecte. Imagina que al tauler de l'administrador d'Escena Viva hi afegim una vista amb l'ocupació global de la temporada: 3.000 sessions al llarg de l'any, amb el seu percentatge, la seva recaptació i la seva comparació amb la temporada anterior.
La implementació ingènua és aquesta:
// src/laboratori/ocupacio-sincrona.js
// Versio INGENUA: recalcula tot dins de la peticio, de manera sincrona.
function calcularOcupacioGlobal(sessions) {
const perSala = new Map();
for (const sessio of sessions) {
// Treball real per sessio: agregacio, formatatge, comparatives...
const clau = sessio.sala;
const acumulat = perSala.get(clau) ?? { aforament: 0, venudes: 0, recaptacioCentims: 0 };
acumulat.aforament += sessio.aforament;
acumulat.venudes += sessio.venudes;
acumulat.recaptacioCentims += sessio.venudes * sessio.preuCentims;
perSala.set(clau, acumulat);
}
return perSala;
}Si aquesta funció triga 300 ms, i un administrador refresca el seu tauler, passa el següent durant aquests 300 ms:
- Totes les peticions de compra d'entrades s'aturen. No s'alenteixen: s'aturen.
- El retard del bucle puja a 300 ms. El percentil 99 de tots els usuaris es dispara.
- Si el servidor atén 200 peticions per segon, 60 persones queden esperant per un tauler que ni tan sols estan mirant.
- I si l'administrador té el tauler amb refresc automàtic cada 5 segons, el servidor passa el 6 % de la seva vida congelat, de manera permanent.
El greu és que això no apareix en desenvolupament. Al teu portàtil, amb un sol usuari, 300 ms és una pàgina que carrega «una mica lenta». En producció, amb trànsit, és una caiguda parcial que cap registre d'errors no et mostrarà, perquè tècnicament no ha fallat res.
Les tres solucions, per ordre de senzillesa:
| Solució | Què fa | On es veu |
|---|---|---|
Trossejar amb setImmediate |
Processa 200 sessions, cedeix el control, continua. El retard màxim baixa a ~20 ms | Aquí mateix, apartat següent |
| Calcular fora i posar a la memòria cau | L'informe es calcula cada 5 minuts en segon pla; la petició només llegeix un valor ja fet | Mòdul 10 |
| Moure el càlcul a un altre fil o procés | worker_threads o cluster: el càlcul passa fora del fil que atén peticions |
Mòdul 10 |
I així queda la versió trossejada, que ja pots escriure amb el que saps avui:
// src/laboratori/ocupacio-trossejada.js
// Versio COOPERATIVA: cedeix el control al bucle cada MIDA_LOT sessions.
const MIDA_LOT = 200;
function calcularOcupacioGlobalTrossejat(sessions, enAcabar) {
const perSala = new Map();
let index = 0;
function processarLot() {
const fi = Math.min(index + MIDA_LOT, sessions.length);
for (; index < fi; index++) {
const sessio = sessions[index];
const acumulat = perSala.get(sessio.sala) ??
{ aforament: 0, venudes: 0, recaptacioCentims: 0 };
acumulat.aforament += sessio.aforament;
acumulat.venudes += sessio.venudes;
acumulat.recaptacioCentims += sessio.venudes * sessio.preuCentims;
perSala.set(sessio.sala, acumulat);
}
if (index < sessions.length) {
// Cedim el control: el bucle aten peticions abans de continuar.
setImmediate(processarLot);
} else {
enAcabar(perSala);
}
}
processarLot();
}El temps total és pràcticament el mateix, fins i tot una mica pitjor. Però el temps de bloqueig continu passa de 300 ms a uns 20 ms, i entre lot i lot el servidor atén compres amb normalitat. És un intercanvi deliberat: sacrifiques una mica de rendiment de l'informe a canvi de no penalitzar ningú més.
Aquest enAcabar(perSala) que veus al final és un callback, i la seva forma no és casual: és el patró que estructura tota l'asincronia clàssica de Node i el tema de la lliçó següent.
Errors Comuns i Consells
Error 1: creure que setTimeout(fn, 100) executa als 100 ms exactes.
Executa com a molt aviat als 100 ms. Si el fil està ocupat, es retarda el que calgui.
Error 2: recolzar-se en l'ordre entre setTimeout(fn, 0) i setImmediate al nivell superior.
És una cursa i canvia entre execucions. Dins d'un callback d'E/S sí que és determinista.
Error 3: fer servir process.nextTick per «ajornar una mica» feina.
Es salta el bucle sencer. En recursió provoca inanició i el procés deixa de respondre sense donar cap error. Fes servir setImmediate.
Error 4: programar terminis llargs amb setTimeout.
Per damunt de 2.147.483.647 ms (~24,8 dies) desborda i s'executa immediatament. Els recordatoris d'Escena Viva aniran a una cua de treballs, no pas a un temporitzador.
Error 5: vigilar la mitjana del retard del bucle en lloc del percentil 99. La mitjana amaga els pics, i els pics són justament el problema.
Error 6: pensar que trossejar amb setImmediate resol el càlcul intensiu.
El fa tolerable, no el resol. Si el càlcul és realment pesat, treure'l del fil (Mòdul 10) és la resposta.
Error 7: creure que les promeses «van en un altre fil».
No. Una promesa es resol a la cua de microtasques, en el mateix fil principal. await no paral·lelitza res per si sol.
Consell 1: aprèn la traça de l'apartat 11 de memòria. Si pots predir aquestes vuit lletres, pots predir qualsevol programa asíncron en Node.
Consell 2: quan alguna cosa «passi en l'ordre equivocat», dibuixa les fases. En el 95 % dels casos la resposta és que estaves barrejant fases diferents del bucle.
Consell 3: instal·la la mesura del retard des del primer dia. Són deu línies de codi i detecta problemes que cap altra mètrica no mostra.
Exercicis
Exercici 1: predir la traça
Sense executar res, escriu l'ordre exacte de la sortida d'aquest programa i justifica cada línia indicant en quina fase o cua s'executa.
const fs = require('node:fs');
console.log('1');
setTimeout(() => {
console.log('2');
setImmediate(() => console.log('3'));
process.nextTick(() => console.log('4'));
}, 0);
setImmediate(() => {
console.log('5');
process.nextTick(() => console.log('6'));
});
fs.readFile(__filename, () => {
console.log('7');
setTimeout(() => console.log('8'), 0);
setImmediate(() => console.log('9'));
});
Promise.resolve().then(() => console.log('10'));
process.nextTick(() => console.log('11'));
console.log('12');Indica també quina és l'única parella de línies l'ordre relatiu de les quals no està garantit.
Exercici 2: detectar el bloqueig a Escena Viva
Escriu src/laboratori/vigilant-bucle.js que:
- Faci servir
monitorEventLoopDelaydenode:perf_hooksper mesurar el retard. - Simuli trànsit amb un
setIntervalde 20 ms que compti peticions ateses. - Executi, als 500 ms, un càlcul síncron sobre 3.000 sessions d'Escena Viva que trigui al voltant de 400 ms.
- Als 2 segons, imprimeixi: peticions ateses davant de peticions esperades, i l'histograma complet (mínim, mitjana, màxim, p50, p99).
- Retorni
process.exitCode = 1si el percentil 99 supera els 50 ms, amb un missatge perstderrexplicant el diagnòstic.
Exercici 3: trossejar l'informe d'ocupació
Partint de calcularOcupacioGlobalTrossejat de l'apartat 15, escriu src/laboratori/comparar-trossejat.js que executi les dues versions (síncrona i trossejada) sobre les mateixes 3.000 sessions mentre un setInterval de 20 ms mesura el trànsit atès, i produeixi una taula comparativa amb:
- Temps total de càlcul de cada versió.
- Bloqueig continu màxim de cada versió.
- Peticions ateses durant cada versió.
Després, respon per escrit: quina mida de lot triaries per a Escena Viva i per què? Què passa si la poses a 1? I a 3000?
Solucions
Solució 1
1 sincron 12 sincron 11 cua de nextTick, en acabar l'script 10 cua de microtasques, despres de la de nextTick 2 fase timers, primera iteracio 4 cua de nextTick, en acabar el callback del timer 5 fase check, primera iteracio 6 cua de nextTick, en acabar el callback de setImmediate 3 fase check, SEGONA iteracio (es va encuar estant ja a la primera) 7 fase poll (callback de fs.readFile) 9 fase check, mateixa iteracio que 7 8 fase timers, iteracio seguent
Justificació dels trams que se solen fallar:
11abans que10: la cua denextTickté prioritat sobre la de microtasques de promesa. Sempre.4just després de2: en acabar el callback del temporitzador es buiden les cues abans de continuar. És el comportament de Node 11+.3després de5i6: quan s'executa el callback del temporitzador (fase timers), programar unsetImmediateel col·loca a la fase check d'aquesta mateixa iteració… però elsetImmediatedel nivell superior (5) ja estava encuat abans, així que va primer. I3es va encuar durant la iteració actual quan ja anàvem camí de check: segons el moment exacte pot entrar a la mateixa cua de check o a la següent. A la pràctica,5sempre precedeix3.7al final del grup:fs.readFileés E/S real. Triga més que tot l'anterior, així que el seu callback arriba en una iteració posterior.9abans que8: dins d'un callback d'E/S (fase poll),setImmediate(check, fase següent) sempre guanya asetTimeout(timers, iteració següent). Aquest és l'ordre determinista de l'apartat 9.2.
L'única parella no garantida del conjunt és la posició relativa de 7 respecte al bloc 2/4/5/6/3: depèn del que trigui el disc a tornar el fitxer. En una màquina amb el fitxer a la memòria cau pot arribar abans del que mostra la traça. Tota la resta és fixa.
Solució 2
// src/laboratori/vigilant-bucle.js
// Mesura l'impacte d'un calcul sincron sobre el transit d'Escena Viva.
const { monitorEventLoopDelay } = require('node:perf_hooks');
const INTERVAL_TRANSIT_MS = 20;
const DURACIO_MS = 2000;
const LLINDAR_P99_MS = 50;
const histograma = monitorEventLoopDelay({ resolution: 5 });
histograma.enable();
const inici = Date.now();
let ateses = 0;
const transit = setInterval(() => {
ateses++;
}, INTERVAL_TRANSIT_MS);
// Cataleg de temporada completa: 3000 sessions.
const sessions = [];
for (let i = 1; i <= 3000; i++) {
sessions.push({
id: `ses-${String(Math.ceil(i / 3)).padStart(3, '0')}-${(i % 3) + 1}`,
sala: ['Teatro Almendra', 'Sala Boveda', 'Auditorio Ribera'][i % 3],
aforament: 420,
venudes: i % 420,
preuCentims: 2500
});
}
// Calcul sincron deliberadament car (~400 ms).
function calcularOcupacioGlobal(sessions) {
const perSala = new Map();
for (const sessio of sessions) {
let soroll = 0;
for (let i = 0; i < 60000; i++) {
soroll += Math.sqrt(i); // Treball artificial.
}
const acumulat = perSala.get(sessio.sala) ??
{ aforament: 0, venudes: 0, recaptacioCentims: 0 };
acumulat.aforament += sessio.aforament;
acumulat.venudes += sessio.venudes;
acumulat.recaptacioCentims += sessio.venudes * sessio.preuCentims;
perSala.set(sessio.sala, acumulat);
}
return perSala;
}
setTimeout(() => {
const t0 = Date.now();
calcularOcupacioGlobal(sessions);
console.error(`[informe] calcul sincron de ${Date.now() - t0} ms`);
}, 500);
setTimeout(() => {
clearInterval(transit);
histograma.disable();
const aMs = (n) => (n / 1e6).toFixed(2);
const esperades = Math.floor((Date.now() - inici) / INTERVAL_TRANSIT_MS);
const p99 = histograma.percentile(99) / 1e6;
console.log('');
console.log('TRANSIT');
console.log(` Peticions esperades : ${esperades}`);
console.log(` Peticions ateses : ${ateses}`);
console.log(` Perdudes : ${esperades - ateses}`);
console.log('');
console.log("RETARD DEL BUCLE D'ESDEVENIMENTS");
console.log(` minim : ${aMs(histograma.min)} ms`);
console.log(` mitjana : ${aMs(histograma.mean)} ms`);
console.log(` maxim : ${aMs(histograma.max)} ms`);
console.log(` p50 : ${aMs(histograma.percentile(50))} ms`);
console.log(` p99 : ${aMs(histograma.percentile(99))} ms`);
if (p99 > LLINDAR_P99_MS) {
console.error('');
console.error(
`DIAGNOSTIC: el percentil 99 (${p99.toFixed(1)} ms) supera el llindar de ` +
`${LLINDAR_P99_MS} ms. Hi ha feina sincrona bloquejant el fil principal. ` +
"Trosseja-la amb setImmediate, posa el resultat a la memoria cau o mou-la a un worker."
);
process.exitCode = 1;
}
}, DURACIO_MS);Sortida típica:
[informe] calcul sincron de 412 ms TRANSIT Peticions esperades : 100 Peticions ateses : 80 Perdudes : 20 RETARD DEL BUCLE D'ESDEVENIMENTS minim : 4.98 ms mitjana : 12.31 ms maxim : 414.20 ms p50 : 5.12 ms p99 : 414.20 ms DIAGNOSTIC: el percentil 99 (414.2 ms) supera el llindar de 50 ms. ...
Fixa't un cop més en la dissociació entre mitjana (12 ms, aparentment sana) i percentil 99 (414 ms, desastrós). És exactament el que veuries en un tauler de producció mal configurat que només mostri mitjanes.
Solució 3
// src/laboratori/comparar-trossejat.js
// Compara el calcul sincron amb el trossejat, mesurant el transit ates.
const MIDA_LOT = Number(process.argv[2]) || 200;
const INTERVAL_TRANSIT_MS = 20;
const sessions = [];
for (let i = 1; i <= 3000; i++) {
sessions.push({
sala: ['Teatro Almendra', 'Sala Boveda', 'Auditorio Ribera'][i % 3],
aforament: 420,
venudes: i % 420,
preuCentims: 2500
});
}
// Treball per sessio, identic en totes dues versions.
function acumular(perSala, sessio) {
let soroll = 0;
for (let i = 0; i < 60000; i++) {
soroll += Math.sqrt(i);
}
const a = perSala.get(sessio.sala) ?? { aforament: 0, venudes: 0, recaptacioCentims: 0 };
a.aforament += sessio.aforament;
a.venudes += sessio.venudes;
a.recaptacioCentims += sessio.venudes * sessio.preuCentims;
perSala.set(sessio.sala, a);
}
function versio1Sincrona() {
const perSala = new Map();
for (const sessio of sessions) acumular(perSala, sessio);
return perSala;
}
function versio2Trossejada(enAcabar) {
const perSala = new Map();
let index = 0;
let bloqueigMaxim = 0;
function processarLot() {
const t0 = Date.now();
const fi = Math.min(index + MIDA_LOT, sessions.length);
for (; index < fi; index++) acumular(perSala, sessions[index]);
bloqueigMaxim = Math.max(bloqueigMaxim, Date.now() - t0);
if (index < sessions.length) setImmediate(processarLot);
else enAcabar(perSala, bloqueigMaxim);
}
processarLot();
}
// --- Mesura ---
let ateses = 0;
const transit = setInterval(() => { ateses++; }, INTERVAL_TRANSIT_MS);
const resultats = [];
// Versio 1
setTimeout(() => {
const base = ateses;
const t0 = Date.now();
versio1Sincrona();
const total = Date.now() - t0;
resultats.push({ versio: 'sincrona', totalMs: total, bloqueigMaxMs: total, ateses: ateses - base });
// Versio 2, tan bon punt acaba la primera.
const base2 = ateses;
const t1 = Date.now();
versio2Trossejada((_, bloqueigMaxim) => {
resultats.push({
versio: `trossejada (lot ${MIDA_LOT})`,
totalMs: Date.now() - t1,
bloqueigMaxMs: bloqueigMaxim,
ateses: ateses - base2
});
clearInterval(transit);
console.table(resultats);
});
}, 200);Resultat típic amb lot de 200:
┌─────────┬────────────────────────┬─────────┬───────────────┬────────┐ │ (index) │ versio │ totalMs │ bloqueigMaxMs │ ateses │ ├─────────┼────────────────────────┼─────────┼───────────────┼────────┤ │ 0 │ 'sincrona' │ 408 │ 408 │ 0 │ │ 1 │ 'trossejada (lot 200)' │ 431 │ 29 │ 21 │ └─────────┴────────────────────────┴─────────┴───────────────┴────────┘
Anàlisi:
- El temps total empitjora un 6 % (408 → 431 ms). Aquest és el preu de cedir el control quinze vegades.
- El bloqueig continu màxim cau de 408 ms a 29 ms: una millora de catorze vegades en allò que de debò afecta els usuaris.
- Les peticions ateses passen de 0 a 21. A la versió síncrona no es va atendre absolutament res.
Resposta raonada sobre la mida de lot:
- Lot d'1: el bloqueig màxim seria mínim (~0,2 ms), però es farien 3.000 voltes del bucle. El temps total es dispararia pel cost de programar i despatxar 3.000
setImmediate. Molta feina administrativa per a molt poc guany addicional. - Lot de 3000: és exactament la versió síncrona, amb el cost extra de la maquinària de trossejat. El pitjor de tots dos mons.
- Elecció per a Escena Viva: un lot calibrat perquè cada tros duri entre 5 i 20 ms. No es tria per nombre d'elements, sinó per temps objectiu, perquè el cost per sessió pot canviar. Un enfocament més robust és un lot adaptatiu: mesurar quant va trigar el lot anterior i ajustar-ne la mida per acostar-se als 10 ms.
I la conclusió honesta: trossejar és un pedaç, molt útil i aplicable avui mateix, però la solució definitiva per al tauler d'ocupació d'Escena Viva és no calcular-lo dins de la petició — posar el resultat a la memòria cau o moure'l a un worker, totes dues coses al Mòdul 10.
Conclusió
Has recorregut el cor de Node.js. Ara saps que el bucle d'esdeveniments és un while en C dins de libuv que gira mentre quedin referències vives, i que cada volta travessa sis fases en ordre fix: timers per als setTimeout vençuts, pending callbacks per a callbacks de sistema ajornats, idle/prepare d'ús intern, poll —on un servidor passa la seva vida, esperant E/S sense gastar CPU—, check per a setImmediate, i close callbacks per als esdeveniments de tancament.
Has entès les decisions de la fase poll: bloqueja esperant esdeveniments, però talla immediatament si hi ha un setImmediate pendent i acota la seva espera si hi ha un temporitzador proper. D'aquí surt la resposta completa a la pregunta clàssica: setTimeout(fn, 0) contra setImmediate és una cursa impredictible al nivell superior, però dins d'un callback d'E/S setImmediate guanya sempre.
Has vist les dues cues que no són fases i que es colen entre cada callback des de Node 11: la de process.nextTick, amb la màxima prioritat, i la de les microtasques de promeses just al darrere. I has executat la traça de les vuit lletres fins a poder-la predir. Saps també que un nextTick recursiu provoca inanició del bucle —un procés que consumeix un nucli sencer i no respon sense donar ni un sol error— i que l'opció segura per ajornar feina és setImmediate. I saps que setTimeout(fn, 0) és en realitat 1 ms, i que per damunt de 24,8 dies desborda i s'executa a l'instant.
Sobretot, tens una eina de diagnòstic real: el retard del bucle d'esdeveniments, mesurat amb monitorEventLoopDelay, vigilat per percentil 99 i no pas per mitjana. I has comprovat a Escena Viva que un informe síncron de 400 ms no alenteix una petició: atura el servidor sencer, deixant sense atendre desenes de persones que només volien comprar una entrada. Trossejar amb setImmediate va baixar el bloqueig de 408 ms a 29 ms; la solució definitiva —posar-ho a la memòria cau o moure-ho a un altre fil— espera al Mòdul 10.
Tot aquest mecanisme existeix per servir una forma concreta d'escriure codi: lliurar a Node una funció que es cridarà quan la feina estigui a punt. Aquesta funció té un nom i una forma canònica en Node, amb l'error sempre en primer lloc, i unes regles que si es trenquen produeixen errors desconcertants. A la lliçó següent, Callbacks i Programació Asíncrona, escriuràs les teves pròpies funcions asíncrones per a Escena Viva, descobriràs per què try/catch deixa de funcionar, i construiràs —expressament— la piràmide de la mort que les promeses vindran a enderrocar.
Curs de Node.js: De Principiant a Avançat
Mòdul 1: Introducció a Node.js
- Què és Node.js?
- Instal·lació i Configuració de l'Entorn
- El Teu Primer Programa en Node.js
- El REPL de Node.js
- JavaScript Modern per a Node.js
- El Projecte del Curs: la Plataforma Escena Viva
Mòdul 2: Conceptes Bàsics
- Arquitectura de Node.js
- El Bucle d'Esdeveniments (Event Loop)
- Callbacks i Programació Asíncrona
- Promeses i async/await
- Esdeveniments i EventEmitter
- Mòduls CommonJS i require()
- Mòduls ES i Interoperabilitat
Mòdul 3: Sistema de Fitxers i E/S
- Lectura i Escriptura de Fitxers
- El Mòdul fs a Fons
- Rutes Multiplataforma amb el Mòdul path
- Treballant amb Streams
- Streams de Transformació i pipeline
- Buffers i Dades Binàries
Mòdul 4: HTTP i Servidors Web
- Creant un Servidor HTTP Simple
- Gestió de Sol·licituds i Respostes
- Enrutament Manual
- Servint Fitxers Estàtics
- Rebent Dades: Cossos de Petició i JSON
- Consumint APIs Externes des de Node.js
Mòdul 5: NPM i Gestió de Paquets
- Introducció a NPM i package.json
- Instal·lació i Ús de Paquets
- Versionat Semàntic i package-lock
- Scripts d'npm i Automatització del Projecte
- Creació i Publicació de Paquets
- Seguretat i Manteniment de Dependències
Mòdul 6: Framework Express.js
- Introducció a Express.js
- Configuració d'una Aplicació Express
- Enrutament a Express
- Middleware
- Middleware de Tercers Essencials
- Validació de Dades d'Entrada
- Gestió d'Errors
Mòdul 7: Bases de Dades i ORMs
- Introducció a les Bases de Dades
- Usant MongoDB amb Mongoose
- Operacions CRUD
- Relacions, Poblat i Consultes Avançades
- Usant Bases de Dades SQL amb Sequelize
- Migracions, Transaccions i Dades de Prova
Mòdul 8: Autenticació i Autorització
- Introducció a l'Autenticació
- Registre d'Usuaris i Hash de Contrasenyes
- Sessions i Galetes amb Passport.js
- Autenticació amb JWT
- Control d'Accés Basat en Rols
- Bones Pràctiques de Seguretat en APIs
Mòdul 9: Proves i Depuració
- Introducció a les Proves
- Proves Unitàries amb Mocha i Chai
- Dobles de Prova amb Sinon
- Proves d'Integració
- Cobertura i Automatització de les Proves
- Depuració d'Aplicacions Node.js
Mòdul 10: Temes Avançats
- El Mòdul Cluster
- Fils de Treball (Worker Threads)
- Memòria Cau i Cues de Treball amb Redis
- Optimització del Rendiment
- Construcció d'APIs RESTful
- GraphQL amb Node.js
Mòdul 11: Desplegament i DevOps
- Configuració i Variables d'Entorn
- Registre i Monitoratge en Producció
- Usant PM2 per a la Gestió de Processos
- Empaquetatge amb Docker
- Desplegant a Heroku i Altres PaaS
- Integració i Desplegament Continus
