La lliçó anterior va acabar amb un diagnòstic precís: els callbacks funcionen, però no retornen res. Com que el resultat futur no és un valor, no es pot guardar en una variable, ni passar a una funció, ni combinar, ni encadenar sense imbricar, i els errors deixen de propagar-se sols així que es travessa la frontera asíncrona. La solució del llenguatge va ser fer que aquell resultat futur sí que sigui un valor: un objecte anomenat promesa que representa «alguna cosa que encara no hi és, però hi serà». En aquesta lliçó aprendràs a crear-les, consumir-les i encadenar-les; veuràs com la piràmide del 05-05 s'aplana en una llista de passos; coneixeràs els quatre combinadors que resolen en una línia el que abans exigia comptadors manuals; i arribaràs a async/await, la sintaxi que fa que el codi asíncron es llegeixi exactament igual que el síncron, amb el seu try/catch funcionant per fi.
Contingut
- Què és una promesa: els tres estats
- Crear una promesa amb
new Promise - Prometificar
llegirBacklogSimulat - Consumir:
.then,.catch,.finally - Encadenar: com s'aplana la piràmide
- Com es propaguen els errors per la cadena
Promise.resolveiPromise.reject- Els quatre combinadors
asynciawaittry/catchque per fi funcionaawaiten bucles davant dePromise.allfor await...ofiawaita nivell de mòdul- Els paranys d'
async/await - Exemple integrador:
carregarTauler() - La taula comparativa dels tres estils
- Errors Habituals i Consells
- Exercicis
- Conclusió
- Què és una promesa: els tres estats
Una promesa (Promise) és un objecte que representa el resultat d'una operació asíncrona. Existeix des de l'instant en què l'operació comença, molt abans que hi hagi resultat, i per això es pot guardar, passar i combinar com qualsevol altre valor.
A cada moment està en un de tres estats:
| Estat | Significat | Com s'hi arriba |
|---|---|---|
| Pendent (pending) | L'operació continua en marxa | Estat inicial de tota promesa |
| Complerta (fulfilled) | Ha acabat bé i té un valor | S'ha cridat resolve(valor) |
| Rebutjada (rejected) | Ha acabat malament i té un motiu | S'ha cridat reject(error), o s'ha llançat una excepció |
stateDiagram-v2
[*] --> Pendent
Pendent --> Complerta: resolve(valor)
Pendent --> Rebutjada: reject(error)
Complerta --> [*]: .then(enComplir)
Rebutjada --> [*]: .catch(enFallar)
Dues propietats que defineixen tot el seu comportament:
- La transició és única i irreversible. Una promesa passa de pendent a complerta o a rebutjada, una sola vegada, i allà es queda. Un cop decidida, es diu que està saldada (settled). Això elimina d'una tacada el problema de la doble crida del 05-05: per molt que el codi cridi
resolvecinc vegades, només compta la primera. - El resultat es conserva. Si et subscrius a una promesa que ja està complerta, en reps el valor igualment. No cal «arribar a temps», al contrari que amb un esdeveniment.
Una diferència de vocabulari que evita confusions: una promesa rebutjada és un resultat normal i previst (el servidor ha retornat un 503), mentre que un error no gestionat és una fallada del programa. Totes dues coses fan servir objectes Error, però conceptualment són diferents.
- Crear una promesa amb
new Promise
new PromiseEl constructor rep una funció —anomenada executor— amb dos paràmetres: resolve i reject.
'use strict';
const promesa = new Promise((resolve, reject) => {
// Aquest cos s'executa IMMEDIATAMENT, de manera síncrona
setTimeout(() => {
const exit = true;
if (exit) resolve('backlog carregat'); // → complerta, amb aquest valor
else reject(new Error('503')); // → rebutjada, amb aquest motiu
}, 400);
});
console.log(promesa); // Promise { <pending> } ← ja existeix, encara sense resultatPunts clau de l'executor:
- S'executa immediatament, en el moment de crear la promesa. El que és asíncron és quan es crida
resolve/reject, no quan comença la feina. resolveirejectsón funcions normals; els seus noms són convenció, no obligació.- Només compta la primera crida. Les següents s'ignoren en silenci.
rejectha de rebre unError, no un string. Només així conserves el missatge, el nom i la traça de 03-05.- Una excepció llançada dins de l'executor rebutja la promesa automàticament. És el primer senyal que la propagació d'errors torna a funcionar.
new Promise(() => {
throw new Error("error a l'executor");
}).catch((e) => console.error('capturat:', e.message)); // capturat: error a l'executorRegla pràctica:
new Promisenomés es fa servir per embolcallar alguna cosa que encara no parla en promeses —unsetTimeout, una API antiga de callbacks—. Si ja tens una promesa, no l'emboliquis en una altra: és un antipatró tan comú que té nom, el constructor promesa explícit.
- Prometificar
llegirBacklogSimulat
llegirBacklogSimulatConvertir una funció de callbacks en una que retorni una promesa s'anomena prometificar, i és l'operació frontissa entre els dos mons.
// js/dades/backlogSimulat.js
import { Tasca } from '../model/tasca.js';
import { ErrorDeDades } from '../model/errors.js';
import { dadesBacklog } from './backlog.js';
/**
* Simula la càrrega del backlog des d'un servidor i retorna una promesa.
* Al Mòdul 7 això serà una crida real amb fetch (07-02).
*
* @param {Object} [opcions]
* @param {number} [opcions.latencia=400]
* @param {boolean} [opcions.fallar=false]
* @returns {Promise<Tasca[]>}
*/
export function llegirBacklog({ latencia = 400, fallar = false } = {}) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (fallar) {
reject(new ErrorDeDades('El servidor de Taller Nómada no respon (503).'));
return;
}
try {
resolve(dadesBacklog.map((dades) => new Tasca(dades)));
} catch (error) {
reject(new ErrorDeDades('El backlog rebut no és vàlid.', error));
}
}, latencia);
});
}Compara-la amb la versió de callbacks del 05-05. La diferència essencial és a la primera línia del cos: hi ha un return. La funció retorna alguna cosa, i aquella cosa és un valor de primera classe:
const promesa = llegirBacklog(); // es pot guardar en una variable
const llista = [llegirBacklog(), llegirBacklog()]; // ficar en un array
funcioQualsevol(llegirBacklog()); // passar com a argumentCap d'aquelles tres línies era possible amb callbacks. I també existeix una recepta genèrica, útil quan prometifiques moltes funcions error-first:
/** Converteix una funció error-first en una que retorna promeses. */
function prometificar(funcioAmbCallback) {
return (...parametres) =>
new Promise((resolve, reject) => {
funcioAmbCallback(...parametres, (error, resultat) => {
if (error) reject(error);
else resolve(resultat);
});
});
}
const llegirBacklogP = prometificar(llegirBacklogSimulat);És la mateixa idea de les funcions d'ordre superior de 03-06: una funció que rep una funció i retorna una altra funció.
- Consumir:
.then, .catch, .finally
.then, .catch, .finallyUna promesa es consumeix registrant què fer quan se saldi.
'use strict';
import { llegirBacklog } from './dades/backlogSimulat.js';
import { Tauler } from './model/tauler.js';
import { AVUI } from './util/dates.js';
console.log('⏳ Carregant…');
llegirBacklog()
.then((tasques) => {
const tauler = new Tauler('Taller Nómada', tasques);
console.log('✓', tauler.resum(AVUI));
// { total: 6, obertes: 5, horesTotals: 48, horesObertes: 45, vencudes: 1, esforc: 124 }
})
.catch((error) => {
console.error('✗', error.message);
})
.finally(() => {
console.log('Càrrega acabada (amb èxit o sense)');
});
console.log("L'aplicació continua responent");| Mètode | Quan s'executa el seu callback | Què rep |
|---|---|---|
.then(enComplir) |
La promesa es compleix | El valor de resolve |
.then(enComplir, enFallar) |
Tots dos casos, cadascun el seu | Valor o motiu |
.catch(enFallar) |
La promesa es rebutja | El motiu del reject |
.finally(enAcabar) |
En qualsevol cas | Res: ni valor ni error |
.finally és l'equivalent del finally de 02-05 i serveix per al mateix: la neteja que cal fer passi el que passi —amagar un indicador de càrrega, tancar una connexió, reactivar un botó—. No rep arguments precisament perquè no ha de dependre del resultat, i no consumeix el valor: el deixa passar als .then següents.
Encara que .then(enComplir, enFallar) existeix, la forma recomanada és .then(...).catch(...), i hi ha un motiu tècnic: amb dos arguments, el gestor d'error no captura les fallades del mateix enComplir, perquè tots dos es registren sobre la mateixa promesa.
// ✗ Si enComplir llança, aquest gestor NO se n'assabenta
promesa.then(enComplir, enFallar);
// ✓ El catch és més avall a la cadena: captura també el que llanci enComplir
promesa.then(enComplir).catch(enFallar);
- Encadenar: com s'aplana la piràmide
Aquí hi ha la propietat que ho canvia tot:
.then()retorna una promesa nova, complerta amb el que retorni el seu callback.
Això permet posar un .then darrere d'un altre en lloc d'imbricar-los. I hi ha una regla addicional que fa la màgia completa: si el callback retorna una promesa, la cadena espera que aquella se saldi i continua amb el seu valor, en lloc de passar la promesa mateixa.
llegirBacklog()
.then((tasques) => new Tauler('Taller Nómada', tasques)) // retorna un valor normal
.then((tauler) => tauler.horesPerResponsable()) // rep el tauler
.then((carrega) => llegirHoresEquip(Object.keys(carrega))) // retorna una PROMESA → s'espera
.then((contractades) => console.log(contractades)); // rep el seu valor, no la promesaAmb aquella eina, la piràmide de l'apartat 9 de 05-05 es converteix en una llista. Primer prometifiquem els altres dos mòduls simulats:
// js/dades/equipSimulat.js
import { ErrorDeDades } from '../model/errors.js';
const CONTRACTES = { 'Iván': 30, 'Marta': 20, 'Lucía': 35 };
export function llegirHoresEquip(noms, latencia = 300) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const desconegut = noms.find((n) => !Object.hasOwn(CONTRACTES, n));
if (desconegut !== undefined) {
reject(new ErrorDeDades(`${desconegut} no figura a l'equip de Taller Nómada.`));
return;
}
resolve(Object.fromEntries(noms.map((n) => [n, CONTRACTES[n]])));
}, latencia);
});
}
export function desarInforme(informe, latencia = 200) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (informe.linies.length === 0) {
reject(new ErrorDeDades('No es desa un informe buit.'));
return;
}
resolve({ desat: true, id: `inf-${Date.now()}`, linies: informe.linies.length });
}, latencia);
});
}I ara l'informe setmanal de la Marta, el mateix del 05-05, sense ni una imbricació:
import { llegirBacklog } from './dades/backlogSimulat.js';
import { llegirHoresEquip, desarInforme } from './dades/equipSimulat.js';
import { Tauler } from './model/tauler.js';
import { AVUI } from './util/dates.js';
let carrega = null; // (mira la nota de després)
console.log("⏳ Generant l'informe setmanal…");
llegirBacklog()
.then((tasques) => {
const tauler = new Tauler('Taller Nómada', tasques);
carrega = tauler.horesPerResponsable();
return llegirHoresEquip(Object.keys(carrega));
})
.then((contractades) => {
const linies = Object.keys(carrega).map((nom) => ({
nom,
assignades: carrega[nom],
contractades: contractades[nom],
sobrecarrega: carrega[nom] > contractades[nom]
}));
return desarInforme({ data: AVUI, linies });
})
.then((rebut) => {
console.log(`✓ Informe ${rebut.id} desat amb ${rebut.linies} línies`);
})
.catch((error) => {
console.error(`✗ ${error.message}`); // UN sol gestor per als tres passos
});Tres millores respecte a la versió amb callbacks, i cap no és cosmètica:
- La sagnia és plana. Els passos es llegeixen de dalt a baix, en l'ordre en què passen.
- Hi ha un únic
.catch. Si falla la càrrega, la consulta de l'equip o el desat, l'error acaba a la mateixa línia. Els tres blocsif (error) { … return; }repetits han desaparegut. - El flux és explícit. Cada
returndiu què es passa al pas següent.
Queda un defecte honest: aquella variable carrega fora de la cadena, necessària perquè el tercer .then necessita una dada que es va calcular al primer. És exactament el mateix problema d'estat compartit que ja va aparèixer en «aplanar amb funcions anomenades» a 05-05, i les promeses per si soles no el resolen del tot. Guarda la incomoditat: async/await la fa desaparèixer, perquè allà les variables són simplement variables locals.
- Com es propaguen els errors per la cadena
Quan un pas falla, la cadena salta tots els .then següents fins al primer .catch. És el comportament de la pila síncrona de 02-05, recuperat.
'use strict';
llegirBacklog({ fallar: true })
.then((tasques) => { console.log('pas 1'); return new Tauler('X', tasques); }) // ← se salta
.then((tauler) => { console.log('pas 2'); return tauler.resum(); }) // ← se salta
.catch((error) => console.error('✗ capturat:', error.message));
// ✗ capturat: El servidor de Taller Nómada no respon (503).Un error dins d'un .then provoca el mateix, encara que no sigui una operació asíncrona:
llegirBacklog()
.then((tasques) => {
return tasques[99].titol; // ✗ TypeError: no existeix la tasca 99
})
.then((titol) => console.log(titol)) // se salta
.catch((error) => console.error('✗', error.name, error.message));
// ✗ TypeError Cannot read properties of undefined (reading 'titol')I —això és clau— la cadena es pot recuperar després d'un .catch, perquè .catch també retorna una promesa:
llegirBacklog({ fallar: true })
.catch((error) => {
console.warn(`⚠ ${error.message}. Fem servir dades locals.`);
return crearBacklog(); // valor de reserva
})
.then((tasques) => {
console.log(`Treballant amb ${tasques.length} tasques`); // Treballant amb 6 tasques
})
.catch((error) => console.error('✗ irrecuperable:', error.message));D'aquí una regla de col·locació important: el .catch captura el que passa per sobre d'ell a la cadena, no per sota. Si el poses al mig i vols que també protegeixi els passos següents, en necessites un altre al final. L'habitual és un sol .catch al final de tot.
Amb errors propis, el patró es combina amb la jerarquia de 05-02:
llegirBacklog()
.then((tasques) => new Tauler('Taller Nómada', tasques))
.then((tauler) => tauler.canviarEstat(6, 'feta')) // ✗ R6: transició prohibida
.catch((error) => {
if (error instanceof ErrorDeValidacio) console.error(`Regla incomplerta: ${error.message}`);
else if (error instanceof ErrorDeDades) console.error(`Dades: ${error.message}`);
else throw error; // fail-fast (02-05)
});
Promise.resolve i Promise.reject
Promise.resolve i Promise.rejectDues dreceres per crear promeses ja saldades.
const jaComplerta = Promise.resolve(42);
const jaRebutjada = Promise.reject(new Error('sense dades'));
jaComplerta.then((v) => console.log(v)); // 42
jaRebutjada.catch((e) => console.error(e.message)); // sense dadesEl seu ús més valuós és normalitzar: fer que una funció retorni sempre una promesa, hagi de treballar de debò o no.
let cacheBacklog = null;
/** Retorna el backlog, del servidor la primera vegada i de la memòria cau després. */
function obtenirBacklog() {
if (cacheBacklog !== null) {
return Promise.resolve(cacheBacklog); // ← mateixa "forma" que el camí llarg
}
return llegirBacklog().then((tasques) => {
cacheBacklog = tasques;
return tasques;
});
}
obtenirBacklog().then((t) => console.log(t.length)); // 6 · després de 400 ms
obtenirBacklog().then((t) => console.log(t.length)); // 6 · gairebé instantaniSense aquell Promise.resolve, la funció retornaria un array unes vegades i una promesa unes altres, i qui la fes servir ho hauria de comprovar. És la memoïtzació de 03-06, ara aplicada a operacions asíncrones.
Promise.resolve té a més una propietat molt pràctica: si li passes una promesa, la retorna tal qual, sense embolicar-la una altra vegada.
- Els quatre combinadors
Aquí hi ha la resposta al «problema 4» del 05-05: combinar operacions asíncrones. Quatre mètodes estàtics, cadascun amb una semàntica diferent.
'use strict';
/** Tasques obertes d'una persona, amb latència proporcional a la seva càrrega. */
function llegirTasquesDe(responsable, latencia = 300) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const seves = dadesBacklog.filter((t) => t.responsable === responsable && t.estat !== 'feta');
if (seves.length === 0) {
reject(new ErrorDeDades(`${responsable} no té tasques obertes.`));
return;
}
resolve({ responsable, tasques: seves.length, hores: seves.reduce((s, t) => s + t.horesEstimades, 0) });
}, latencia);
});
}Promise.all — espera que totes es compleixin. Si una falla, falla el conjunt immediatament.
Promise.all([llegirTasquesDe('Iván', 300), llegirTasquesDe('Marta', 500), llegirTasquesDe('Lucía', 200)])
.then((resultats) => {
for (const r of resultats) console.log(`${r.responsable}: ${r.tasques} tasques, ${r.hores} h`);
console.log('Total obertes:', resultats.reduce((s, r) => s + r.hores, 0));
})
.catch((error) => console.error('✗', error.message));
// Iván: 3 tasques, 25 h
// Marta: 1 tasques, 6 h
// Lucía: 1 tasques, 14 h
// Total obertes: 45Dues coses que cal interioritzar. L'ordre de l'array de resultats és el d'entrada, no el d'arribada: encara que la Lucía respongui primer, el seu resultat continua a la tercera posició. I les tres peticions corren alhora: el total triga 500 ms —el que la més lenta—, no 1000 ms.
Promise.allSettled — espera que totes se saldin, complertes o rebutjades, i no falla mai.
Promise.allSettled([llegirTasquesDe('Iván'), llegirTasquesDe('Ningú'), llegirTasquesDe('Lucía')])
.then((resultats) => {
for (const r of resultats) {
if (r.status === 'fulfilled') console.log(`✓ ${r.value.responsable}: ${r.value.hores} h`);
else console.warn(`⚠ ${r.reason.message}`);
}
});
// ✓ Iván: 25 h
// ⚠ Ningú no té tasques obertes.
// ✓ Lucía: 14 hCada element és un objecte { status: 'fulfilled', value } o { status: 'rejected', reason }. És el que vols quan una fallada parcial és acceptable: prefereixes l'informe de dues persones a cap informe.
Promise.race — se salda amb la primera que acabi, tant si es compleix com si es rebutja. El seu ús canònic és imposar un temps màxim:
function ambTempsMaxim(promesa, ms) {
const rellotge = new Promise((_, reject) =>
setTimeout(() => reject(new ErrorDeDades(`Temps esgotat després de ${ms} ms.`)), ms));
return Promise.race([promesa, rellotge]);
}
ambTempsMaxim(llegirBacklog({ latencia: 3000 }), 1000)
.then((tasques) => console.log(tasques.length))
.catch((error) => console.error('✗', error.message));
// ✗ Temps esgotat després de 1000 ms.Un avís honest: el setTimeout intern no cancel·la l'operació lenta, que continua corrent encara que el seu resultat s'ignori. La cancel·lació de debò necessita AbortController, que arriba a 07-03.
Promise.any — es compleix amb la primera que es compleixi, ignorant els rebuigs. Només falla si fallen totes, amb un AggregateError.
Promise.any([llegirBacklog({ latencia: 800 }), llegirBacklog({ latencia: 300, fallar: true }), llegirBacklog({ latencia: 500 })])
.then((tasques) => console.log(`✓ Rebudes ${tasques.length} tasques del primer servidor que ha respost`))
.catch((error) => console.error('✗ Han fallat totes:', error.errors.length));
// ✓ Rebudes 6 tasques del primer servidor que ha respost (el de 500 ms)La taula de decisió:
| Combinador | Es compleix quan… | Es rebutja quan… | Fes-lo servir per a |
|---|---|---|---|
Promise.all |
Totes es compleixen | Alguna falla (a l'instant) | Necessites totes les dades per continuar |
Promise.allSettled |
Totes se salden | Mai | Una fallada parcial és tolerable; vols l'informe complet |
Promise.race |
La primera es compleix | La primera falla | Temps màxims, «el que abans contesti» |
Promise.any |
La primera que es compleixi | Totes fallen | Diverses fonts equivalents; val qualsevol |
Tots quatre accepten qualsevol iterable i retornen una promesa. Compara mentalment aquestes quatre línies amb les vint de comptadors i banderes de l'apartat 10 de 05-05: això és el que significa que el resultat futur sigui un valor.
async i await
async i awaitLes promeses van arreglar el flux, però el codi continua ple de .then((x) => …). Des d'ES2017 existeix una sintaxi que elimina fins i tot això.
async davant d'una funció fa que retorni sempre una promesa.
'use strict';
async function saludar() {
return 'hola';
}
console.log(saludar()); // Promise { 'hola' } ← no el string
saludar().then((v) => console.log(v)); // 'hola'El que fa la funció async |
La promesa que retorna |
|---|---|
return valor |
Es compleix amb valor |
return unaPromesa |
Es compleix (o rebutja) amb el que doni aquella promesa |
throw error |
Es rebutja amb aquell error |
| No retorna res | Es compleix amb undefined |
await davant d'una promesa n'espera el resultat i el retorna com si fos un valor normal. Només es pot fer servir dins d'una funció async (o al nivell superior d'un mòdul, apartat 12).
async function carregar() {
console.log('⏳ Carregant…');
const tasques = await llegirBacklog(); // "espera" sense bloquejar el fil
console.log(`✓ ${tasques.length} tasques`);
return tasques;
}Que aquell await no bloqueja res és l'essencial i el que més costa de creure al principi. El que fa és suspendre aquesta funció en aquell punt i tornar el control al programa; quan la promesa se salda, la funció es reprèn just on s'havia quedat. El fil, mentrestant, està lliure per a tota la resta. El mecanisme exacte que ho permet és el tema de 05-07.
La mateixa cadena de l'apartat 5, reescrita:
async function generarInformeSetmanal() {
const tasques = await llegirBacklog();
const tauler = new Tauler('Taller Nómada', tasques);
const carrega = tauler.horesPerResponsable(); // ← variable local normal
const contractades = await llegirHoresEquip(Object.keys(carrega));
const linies = Object.keys(carrega).map((nom) => ({
nom,
assignades: carrega[nom],
contractades: contractades[nom],
sobrecarrega: carrega[nom] > contractades[nom]
}));
const rebut = await desarInforme({ data: AVUI, linies });
console.log(`✓ Informe ${rebut.id} desat amb ${rebut.linies} línies`);
return linies;
}Compara-ho amb la piràmide original de 05-05. És el mateix programa: tres operacions asíncrones encadenades, cadascuna dependent de l'anterior. I es llegeix exactament com es llegiria si tot fos síncron. La variable carrega ha tornat a ser una variable local, sense necessitat de treure-la a fora. No hi ha callbacks, no hi ha .then, no hi ha sagnia.
Un apunt de sintaxi: async funciona amb qualsevol forma de funció, incloses les fletxa de 03-02 i els mètodes de classe de 05-02.
const carregar = async () => { … }; // fletxa
class Tauler { async recarregar() { … } } // mètode
[1, 2].map(async (n) => { … }); // callback (compte: retorna promeses)
try/catch que per fi funciona
try/catch que per fi funcionaA 02-05 vas aprendre try/catch, i a 05-05 vas descobrir que no servia per al que és asíncron. Amb await, torna a servir.
'use strict';
async function carregarAmbAvis() {
try {
const tasques = await llegirBacklog({ fallar: true });
return new Tauler('Taller Nómada', tasques);
} catch (error) {
if (error instanceof ErrorDeDades) {
console.warn(`⚠ ${error.message} Fem servir el backlog local.`);
return new Tauler('Taller Nómada (local)', crearBacklog());
}
throw error; // el que no sé gestionar, que pugi
} finally {
console.log('Intent de càrrega finalitzat');
}
}
const tauler = await carregarAmbAvis();
console.log(tauler.resum(AVUI));
// ⚠ El servidor de Taller Nómada no respon (503). Fem servir el backlog local.
// Intent de càrrega finalitzat
// { total: 6, obertes: 5, horesTotals: 48, horesObertes: 45, vencudes: 1, esforc: 124 }El motiu pel qual ara sí que funciona és precís: await converteix un rebuig en una excepció llançada en aquella mateixa línia. Com que el throw passa dins del try, a la mateixa pila, el catch el captura igual que capturaria un TypeError. Tota la maquinària de 02-05 torna a estar disponible: finally, instanceof per distingir tipus, rellançar el que no saps gestionar, la jerarquia ErrorDeValidacio/ErrorDeDades de 05-02.
I hi ha una simetria que convé tenir present:
| Al codi | Efecte sobre la promesa retornada |
|---|---|
throw error dins d'una funció async |
La promesa es rebutja |
await promesaRebutjada |
Es llança una excepció en aquella línia |
Són la mateixa porta en els dos sentits, i per això els errors travessen sense problema la frontera entre funcions async i cadenes de promeses.
També pots barrejar estils quan convingui. Un patró útil és capturar només un await concret sense embolcallar mitja funció:
await en bucles davant de Promise.all
await en bucles davant de Promise.allAquest és l'error de rendiment més comú de tot async/await, i mereix un apartat propi.
'use strict';
// ✗ LENT: cada iteració espera l'anterior
async function carregarTotsSequencial() {
const inici = Date.now();
const resultats = [];
for (const nom of ['Iván', 'Marta', 'Lucía']) {
resultats.push(await llegirTasquesDe(nom, 300)); // 300 + 300 + 300
}
console.log(`Seqüencial: ${Date.now() - inici} ms`); // ≈ 900 ms
return resultats;
}
// ✓ RÀPID: les tres arrenquen alhora
async function carregarTotsParallel() {
const inici = Date.now();
const promeses = ['Iván', 'Marta', 'Lucía'].map((n) => llegirTasquesDe(n, 300));
const resultats = await Promise.all(promeses); // max(300, 300, 300)
console.log(`Paral·lel: ${Date.now() - inici} ms`); // ≈ 300 ms
return resultats;
}Nou-cents mil·lisegons davant de tres-cents, amb tres elements. Amb trenta responsables serien nou segons davant de tres-cents mil·lisegons. La causa és que await dins d'un bucle atura el bucle: la segona petició ni tan sols s'envia fins que arriba la primera.
La clau de l'arranjament és en un detall que convé subratllar: una promesa comença a treballar en el moment en què es crea, no quan se li fa await. El .map() crea les tres promeses de cop —i les tres peticions surten alhora—; l'await Promise.all(...) només espera que totes acabin.
flowchart LR
subgraph Seq["Seqüencial · 900 ms"]
A1["Iván<br/>0→300"] --> A2["Marta<br/>300→600"] --> A3["Lucía<br/>600→900"]
end
subgraph Par["Paral·lel · 300 ms"]
B1["Iván 0→300"]
B2["Marta 0→300"]
B3["Lucía 0→300"]
end
Ara bé: seqüencial no sempre està malament. És el correcte quan cada pas necessita el resultat de l'anterior, o quan vols limitar deliberadament la càrrega sobre el servidor.
| Situació | Forma correcta |
|---|---|
| Les operacions són independents | Promise.all amb map |
| Cadascuna necessita el resultat de l'anterior | for...of amb await a dins |
| Independents, però toleres fallades parcials | Promise.allSettled |
| Independents, però no vols saturar el servidor | Bucle amb await, o lots de N |
I un advertiment sobre forEach, que és un parany clàssic:
// ✗ NO espera: forEach ignora les promeses que retorna el seu callback
noms.forEach(async (n) => {
const r = await llegirTasquesDe(n);
console.log(r.hores);
});
console.log('Acabat?'); // surt ABANS que els resultatsforEach (04-04) descarta el valor de retorn del callback, així que les tres promeses queden soltes i ningú no les espera. Si necessites esperar, fes servir for...of amb await (seqüencial) o map + Promise.all (paral·lel). Mai forEach.
for await...of i await a nivell de mòdul
for await...of i await a nivell de mòdulQuan el que tens és un array de promeses i vols processar-les a mesura que arriben, existeix una variant del bucle:
async function mostrarAMesuraQueArriben() {
const promeses = ['Iván', 'Marta', 'Lucía'].map((n) => llegirTasquesDe(n));
for await (const resultat of promeses) {
console.log(`${resultat.responsable}: ${resultat.hores} h`);
}
}Les tres peticions arrenquen alhora (el map ja les va crear), i el bucle les va desembolicant en ordre d'array. El seu potencial de debò és recórrer fonts de dades asíncrones de longitud desconeguda —pàgines de resultats, fitxers que es llegeixen per trossos—, i per a això calen els iteradors asíncrons, que s'estudien a 05-08. Per ara n'hi ha prou amb reconèixer la sintaxi.
L'altre afegit és l'await a nivell de mòdul, que ja va aparèixer de passada a 05-04:
// js/dades/configuracio.js
export const config = await llegirConfiguracioSimulada(); // ✓ només en un mòdul ESTot mòdul que importi aquest esperarà que aquell await acabi abans d'executar-se. És còmode per a configuració imprescindible, però endarrereix l'arrencada de tota l'aplicació, així que convé fer-lo servir amb moderació i només a la capa de dades.
- Els paranys d'
async/await
async/awaitQuatre errors que cometràs alguna vegada. Convé reconèixer-los pel seu símptoma.
Parany 1: oblidar l'await. El símptoma és un Promise { <pending> } on esperaves una dada.
async function dolent() {
const tasques = llegirBacklog(); // ✗ falta await
console.log(tasques.length); // undefined (una promesa no té .length)
return tasques.filter((t) => t.oberta); // ✗ TypeError: tasques.filter is not a function
}Parany 2: oblidar el return. La funció async es compleix amb undefined i ningú no se n'assabenta.
async function tambeDolent() {
llegirBacklog().then((t) => t.length); // ✗ la promesa s'ignora
} // retorna Promise { undefined }
async function be() {
return (await llegirBacklog()).length; // ✓
}Un cas especialment insidiós és oblidar l'await dins d'un try:
async function errorQueEscapa() {
try {
return llegirBacklog({ fallar: true }); // ✗ sense await: el rebuig passa FORA del try
} catch (error) {
console.error('mai no arribo aquí');
}
}Sense await, la funció retorna la promesa abans que es rebutgi, i el catch no veu res. La regla és return await quan el return és dins d'un try; fora d'un try, return promesa és equivalent i una mica més eficient.
Parany 3: rebuigs sense gestionar. Una promesa que es rebutja i no té cap .catch ni try/catch produeix un UnhandledPromiseRejection. Al navegador apareix com un error a la consola; a Node modern, acaba el procés.
llegirBacklog({ fallar: true }); // ✗ ningú no escolta
llegirBacklog({ fallar: true }).catch(console.error); // ✓Especialment traïdores són les promeses «òrfenes» que es llancen sense esperar (fire and forget). Si de debò no t'interessa el resultat, posa-li un .catch igualment, encara que només registri la fallada.
Parany 4: async en un constructor. No existeix: un constructor no pot ser async perquè ha de retornar l'objecte, no una promesa. El patró correcte és una fàbrica estàtica de les que vas aprendre a 05-02:
class Tauler {
static async carregar(nom) {
const tasques = await llegirBacklog();
return new Tauler(nom, tasques);
}
}
const tauler = await Tauler.carregar('Taller Nómada');
- Exemple integrador:
carregarTauler()
carregarTauler()Reunim-ho tot a la funció que a partir d'ara serà l'arrencada de Nómada Tasques: carregar el backlog, validar-lo, construir el tauler i produir el resum, amb reserva local si el servidor falla i temps màxim d'espera.
// js/dades/carrega.js
import { Tasca } from '../model/tasca.js';
import { Tauler } from '../model/tauler.js';
import { ErrorDeDades, ErrorDeValidacio } from '../model/errors.js';
import { llegirBacklog } from './backlogSimulat.js';
import { crearBacklog } from './backlog.js';
import { AVUI } from '../util/dates.js';
/** Rebutja si la promesa no se salda en el termini indicat. */
function ambTempsMaxim(promesa, ms) {
const rellotge = new Promise((_, reject) =>
setTimeout(() => reject(new ErrorDeDades(`Temps esgotat després de ${ms} ms.`)), ms));
return Promise.race([promesa, rellotge]);
}
/** Comprova els invariants del backlog rebut. Llança si alguna cosa no quadra. */
function validarBacklog(tasques) {
if (!Array.isArray(tasques) || tasques.length === 0) {
throw new ErrorDeDades('El backlog és buit o no és un array.');
}
const ids = new Set(tasques.map((t) => t.id));
if (ids.size !== tasques.length) {
throw new ErrorDeValidacio('Hi ha identificadors repetits al backlog.', 'id', null); // R1
}
const impostor = tasques.find((t) => !(t instanceof Tasca));
if (impostor !== undefined) {
throw new ErrorDeDades('El backlog conté elements que no són tasques.');
}
return tasques;
}
/**
* Carrega, valida i resumeix el backlog de Taller Nómada.
* @param {Object} [opcions]
* @param {number} [opcions.tempsMaxim=2000]
* @param {boolean}[opcions.permetreReserva=true]
* @returns {Promise<{tauler: Tauler, resum: Object, origen: string}>}
*/
export async function carregarTauler({ tempsMaxim = 2000, permetreReserva = true } = {}) {
let tasques;
let origen = 'servidor';
try {
tasques = validarBacklog(await ambTempsMaxim(llegirBacklog(), tempsMaxim));
} catch (error) {
if (!permetreReserva) throw error;
console.warn(`⚠ ${error.message} Es fan servir les dades locals.`);
tasques = validarBacklog(crearBacklog());
origen = 'local';
}
const tauler = new Tauler('Taller Nómada', tasques);
return { tauler, resum: tauler.resum(AVUI), origen };
}I el punt d'entrada, que queda notablement curt:
// js/app.js
import { carregarTauler } from './dades/carrega.js';
import { llegirHoresEquip } from './dades/equipSimulat.js';
import { AVUI, dataLlegible } from './util/dates.js';
async function iniciar() {
console.log(`— Taller Nómada · ${dataLlegible(AVUI)} —`);
console.log('⏳ Carregant…');
const { tauler, resum, origen } = await carregarTauler();
for (const tasca of tauler.tasques) console.log(tasca.descripcio(AVUI));
console.log(`\nOrigen de les dades: ${origen}`);
console.log(`${resum.total} tasques, ${resum.obertes} obertes`);
console.log(`Hores: ${resum.horesObertes} obertes de ${resum.horesTotals} totals`);
console.log(`Vençudes: ${resum.vencudes} · Esforç ponderat: ${resum.esforc}`);
const carrega = tauler.horesPerResponsable();
const contractades = await llegirHoresEquip(Object.keys(carrega));
console.log('\nCàrrega per responsable:');
for (const [persona, hores] of Object.entries(carrega)) {
console.log(` ${persona.padEnd(8)} ${String(hores).padStart(3)} h / ${contractades[persona]} h contractades`);
}
}
iniciar().catch((error) => console.error('✗ Error fatal:', error.message));— Taller Nómada · 20 de setembre de 2026 — ⏳ Carregant… ▸ [1] Redissenyar la sala polivalent · Iván · 12 h ○ [2] Cartelleria del taller de serigrafia · Marta · 6 h ○ [3] Actualitzar el web de reserves · Lucía · 14 h ✓ [4] Inventari de tintes de serigrafia · Marta · 3 h ▸ [5] Guia sobre enquadernació per a residents · Iván · 8 h ○ [6] Pressupost de la fusteria · Iván · 5 h ⚠ VENÇUDA Origen de les dades: servidor 6 tasques, 5 obertes Hores: 45 obertes de 48 totals Vençudes: 1 · Esforç ponderat: 124 Càrrega per responsable: Iván 25 h / 30 h contractades Marta 6 h / 20 h contractades Lucía 14 h / 35 h contractades
Fixa't en el .catch de l'última línia: iniciar() és una funció async, retorna una promesa, i si alguna cosa s'escapa de tots els gestors interns aquell .catch és la xarxa de seguretat que evita un rebuig sense gestionar. Tota funció async que s'invoca des del nivell superior necessita aquell tancament.
- La taula comparativa dels tres estils
| Aspecte | Callbacks | Promeses (.then) |
async/await |
|---|---|---|---|
| L'operació retorna un valor? | No | Sí, una promesa | Sí, una promesa |
| Forma del codi encadenat | Piràmide imbricada | Cadena plana | Lineal, com el síncron |
| Gestió d'errors | if (error) a cada nivell |
Un .catch per a tota la cadena |
try/catch normal |
Funciona el try/catch? |
No | Només dins de cada callback | Sí |
| Propagació automàtica | No | Sí, per la cadena | Sí, com a excepcions |
| Operacions en paral·lel | Comptadors manuals | Promise.all i companyia |
await Promise.all |
| Doble crida al resultat | Possible | Impossible (estat irreversible) | Impossible |
| Inversió de control | Sí: lliures la teva funció | No: reps un objecte | No |
| Variables entre passos | Compartides fora | De vegades fora de la cadena | Locals normals |
| Llegibilitat amb 5 passos | Molt dolenta | Acceptable | Bona |
La conclusió operativa: escriu async/await per defecte, fes servir .then/.catch quan encaixi millor —una transformació solta, un .catch de reserva en una línia—, recorre als combinadors per a tot el que sigui paral·lel, i reserva new Promise per embolcallar APIs antigues de callbacks. I no oblidis que els tres estils són el mateix mecanisme: async/await és sintaxi sobre promeses, i una promesa se salda gràcies a callbacks registrats per dins.
Errors Habituals i Consells
- Oblidar
await. El símptoma ésPromise { <pending> },undefineden una propietat o unTypeErrordient que un mètode no és una funció. És la primera hipòtesi quan alguna cosa asíncrona dona resultats estranys. - Oblidar
awaitdins d'untry. El rebuig s'escapa del bloc i elcatchno se n'assabenta. Fes servirreturn awaitquan elreturnsigui dins d'untry. awaitdins d'un bucle sobre operacions independents. Multiplica el temps total pel nombre d'elements. Fes servirmap+Promise.all.forEachamb un callbackasync. No espera res. Fes servirfor...ofomap+Promise.all.- Promeses sense
.catch. Un rebuig sense gestionar és un error al navegador i pot tombar el procés a Node. Tota cadena acaba en.catch, i tota funcióasynccridada des del nivell superior també. - Embolcallar una promesa en
new Promise. Si ja tens una promesa, encadena-la.new Promiseés només per embolcallar el que encara no ho és. reject('text')amb un string. Perds nom i traça. Rebutja sempre amb unErroro una subclasse de les de 05-02.Promise.allquan toleres fallades parcials. Un sol rebuig tira tot el conjunt. Si prefereixes resultats parcials,allSettled.- Creure que
Promise.racecancel·la. No cancel·la res: l'operació perdedora continua corrent. La cancel·lació real ésAbortController(07-03). - Constructors
async. No existeixen. Fes servir una fàbrica estàtica (static async carregar()). - Consell: anomena les variables que contenen promeses de manera que es noti (
promesaBacklog,pendents). Una variable anomenadatasquesque en realitat és una promesa és una font inesgotable de despistes.
Exercicis
Exercici 1 — De callbacks a async/await. Reescriu la funció ambReintents de l'exercici 3 de 05-05 com a ambReintents(operacio, intents, esperaMs) on operacio retorna una promesa. Ha de reintentar fins a esgotar els intents, esperar entre ells i llançar l'últim error si tots fallen. Compta les línies i compara-les amb la versió de callbacks.
Exercici 2 — Informe paral·lel amb tolerància a fallades. Escriu informeDeCarrega(noms) que consulti les hores de diverses persones en paral·lel, inclogui a l'informe qui respongui i llisti a part qui falli, sense que una fallada tombi el conjunt. Prova-la amb ['Iván', 'Marta', 'Ningú', 'Lucía'] i verifica que les 45 h obertes continuen quadrant entre els tres reals.
Exercici 3 — Predir la sortida. Sense executar-lo, indica què imprimeix i en quin ordre, i explica cada línia. (Nota: aquí només es demana el resultat; el perquè exacte de l'ordre entre el que és síncron i el que és asíncron és el tema de 05-07.)
async function pas(nom, ms, fallar = false) {
await new Promise((r) => setTimeout(r, ms));
if (fallar) throw new Error(`${nom} ha fallat`);
console.log(`fi ${nom}`);
return nom;
}
async function principal() {
console.log('A');
const p1 = pas('un', 200);
const p2 = pas('dos', 100, true);
console.log('B');
try {
const r = await Promise.all([p1, p2]);
console.log('C', r);
} catch (e) {
console.log('D', e.message);
}
const r2 = await Promise.allSettled([pas('tres', 50), pas('quatre', 50, true)]);
console.log('E', r2.map((x) => x.status));
}
principal();
console.log('F');Solucions
Exercici 1
'use strict';
const esperar = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
/**
* Executa una operació que retorna una promesa, reintentant-la si falla.
* @param {Function} operacio () => Promise
* @param {number} intents
* @param {number} esperaMs
*/
async function ambReintents(operacio, intents, esperaMs = 200) {
let ultimError;
for (let i = 1; i <= intents; i++) {
try {
return await operacio();
} catch (error) {
ultimError = error;
if (i < intents) {
console.log(` ↻ intent ${i} fallit: ${error.message}. En queden ${intents - i}.`);
await esperar(esperaMs);
}
}
}
throw new ErrorDeDades(`Han fallat els ${intents} intents. Últim error: ${ultimError.message}`, ultimError);
}
// Prova
let vegades = 0;
const servidorInestable = () => {
vegades += 1;
const intent = vegades;
return new Promise((resolve, reject) => {
setTimeout(() => {
if (intent < 3) reject(new Error(`503 a l'intent ${intent}`));
else resolve({ backlog: 6, hores: 48 });
}, 100);
});
};
const dades = await ambReintents(servidorInestable, 4);
console.log('✓ Rebut:', dades);
// ↻ intent 1 fallit: 503 a l'intent 1. En queden 3.
// ↻ intent 2 fallit: 503 a l'intent 2. En queden 2.
// ✓ Rebut: { backlog: 6, hores: 48 }Dotze línies de funció davant de les vint de la versió amb callbacks, i la comparació qualitativa és més eloqüent que el recompte: aquí tota la lògica és lògica. Un for normal compta els intents, un try/catch normal captura la fallada, un await normal espera entre reintents. Ha desaparegut la funció interna que existia només per poder repetir-se, han desaparegut els return de guarda després de cada crida al callback, i ha desaparegut la fragilitat de la doble crida: una promesa se salda una sola vegada per construcció. Fixa't també en el return await operacio() dins del try: aquí l'await és imprescindible, perquè sense ell el rebuig s'escaparia del bloc i no hi hauria reintent (parany 2 de l'apartat 13).
Exercici 2
'use strict';
/**
* Consulta la càrrega de diverses persones en paral·lel, tolerant fallades.
* @param {string[]} noms
* @returns {Promise<{linies: Array, fallades: Array, horesTotals: number}>}
*/
async function informeDeCarrega(noms) {
const resultats = await Promise.allSettled(noms.map((n) => llegirTasquesDe(n)));
const linies = [];
const fallades = [];
for (const [i, r] of resultats.entries()) {
if (r.status === 'fulfilled') linies.push(r.value);
else fallades.push({ nom: noms[i], motiu: r.reason.message });
}
return {
linies,
fallades,
horesTotals: linies.reduce((s, l) => s + l.hores, 0)
};
}
const informe = await informeDeCarrega(['Iván', 'Marta', 'Ningú', 'Lucía']);
for (const l of informe.linies) console.log(`✓ ${l.responsable.padEnd(8)} ${l.tasques} tasques · ${l.hores} h`);
for (const f of informe.fallades) console.warn(`⚠ ${f.nom}: ${f.motiu}`);
console.log(`Total d'hores obertes: ${informe.horesTotals}`);
// ✓ Iván 3 tasques · 25 h
// ✓ Marta 1 tasques · 6 h
// ✓ Lucía 1 tasques · 14 h
// ⚠ Ningú: Ningú no té tasques obertes.
// Total d'hores obertes: 45Les 45 h canòniques quadren: 25 de l'Iván, 6 de la Marta i 14 de la Lucía. Tres decisions que mereixen comentari. L'elecció d'allSettled en lloc d'all és el cor de l'exercici: amb all, la fallada de 'Ningú' hauria tirat l'informe complet i no n'hauries vist ni una línia. El resultats.entries() és necessari perquè allSettled no diu a quina entrada correspon cada resultat; l'únic garantit és que l'ordre es conserva, i per això l'índex serveix per recuperar el nom. I les quatre consultes es llancen alhora al map, així que l'informe triga el que la més lenta, no la suma de les quatre.
Exercici 3
Línia per línia:
AiBsón síncrones dins deprincipal. Entremig es creenp1ip2:pasésasync, i el seu cos s'executa fins al primerawait, així que els dos temporitzadors arrenquen immediatament i en paral·lel. Cap dels dos no imprimeix res encara.Fsurt després deBperquèprincipal()se suspèn al seu primerawaiti torna el control al nivell superior, que encara tenia aquellconsole.logpendent.D dos ha fallat: als 100 msp2es rebutja.Promise.alles rebutja a l'instant amb la primera fallada, sense esperarp1. Per això no s'imprimeixC.- Mai no apareix
fi dos(va llançar abans d'arribar alconsole.log), però sí que apareixeriafi unals 200 ms:p1continua el seu curs encara quePromise.allja l'hagi ignorada —recorda que res no es cancel·la—. El seufi unes cola entreDiE. E [ 'fulfilled', 'rejected' ]:allSettledespera totes dues i no es rebutja mai;'tres'imprimeix el seufi tresi'quatre'falla sense imprimir.
Amb fi un inclòs, la sortida completa és: A, B, F, D dos ha fallat, fi un, fi tres, E [...]. La lliçó que cal endur-se és doble: Promise.all falla ràpid però no cancel·la res, i una funció async cedeix el control al seu primer await, que és exactament el mecanisme que es dissecciona a la lliçó següent.
Conclusió
Has resolt els quatre problemes que va deixar la lliçó anterior, i tots amb la mateixa idea: fer que el resultat futur sigui un valor. Una promesa és un objecte amb tres estats —pendent, complerta, rebutjada—, amb una transició única i irreversible que fa impossible la doble crida, i amb el resultat conservat per a qui s'hi subscrigui més tard. Saps crear-la amb new Promise((resolve, reject) => …) —recordant que l'executor s'executa immediatament, que només compta la primera crida i que cal rebutjar amb Error—, i saps prometificar una API de callbacks, que és l'operació frontissa entre els dos mons i la que va convertir llegirBacklogSimulat en llegirBacklog.
Domines el consum. .then per al valor, .catch per a la fallada, .finally per a la neteja; i sobretot l'encadenament, perquè cada .then retorna una promesa nova i, si el seu callback en retorna una altra, la cadena l'espera. Això va aplanar la piràmide de l'informe setmanal en una llista de passos amb un sol .catch al final, i va restaurar la propagació automàtica d'errors que s'havia perdut en travessar la frontera asíncrona: una fallada en qualsevol baula salta fins al primer gestor, i un .catch intermedi pot fins i tot recuperar la cadena retornant un valor de reserva. Amb Promise.resolve normalitzes funcions que de vegades treballen i de vegades responen de memòria cau, i amb els quatre combinadors resols en una línia el que exigia comptadors manuals: all quan ho necessites tot, allSettled quan toleres fallades parcials, race per a temps màxims i any per a diverses fonts equivalents.
I has arribat a async/await, que és sintaxi sobre tot l'anterior. Una funció async retorna sempre una promesa —es compleix amb el return, es rebutja amb el throw—, i await suspèn aquella funció sense bloquejar el fil fins que la promesa se salda, lliurant el valor com si fos normal. Amb això, el try/catch torna a funcionar, perquè await converteix un rebuig en una excepció llançada en aquella mateixa línia, i amb ell tornen finally, l'instanceof per distingir ErrorDeValidacio d'ErrorDeDades, i el fail-fast de rellançar el que no saps gestionar. Coneixes l'error de rendiment clàssic —await dins d'un bucle sobre operacions independents, 900 ms en lloc de 300— i el seu arranjament amb map + Promise.all, recolzat en el detall que una promesa comença a treballar en crear-se, no en esperar-la; saps que forEach amb un callback async no espera res; i reconeixes for await...of i l'await a nivell de mòdul. Els quatre paranys —oblidar l'await, oblidar el return dins d'un try, deixar rebuigs sense gestionar, intentar un constructor async— estan identificats pel seu símptoma. I carregarTauler() ho reuneix tot: càrrega amb temps màxim, valida els invariants del backlog, cau a dades locals si el servidor falla i retorna el resum canònic de sempre —48 h, 45 obertes, 1 vençuda, esforç 124— en una funció que es llegeix de dalt a baix.
Queda una pregunta que hem anat esquivant expressament. Saps què fa await, però no per què l'ordre de la sortida és el que és: per què 'F' surt abans que el resultat d'una promesa ja complerta, per què un setTimeout(f, 0) s'executa després d'un .then registrat més tard, i per què un bucle pesat congela la interfície encara que hi hagi await pel mig. Tot això ho decideix una maquinària amb nom propi —la pila de crides, les APIs de l'entorn, la cua de macrotasques, la cua de microtasques i el bucle que les coordina— i és el tema d'El Bucle d'Esdeveniments i la Cua de Microtasques.
Curs de JavaScript: De Principiant a Avançat
Mòdul 1: Introducció a JavaScript
- Què és JavaScript?
- Configuració del teu Entorn de Desenvolupament
- El teu Primer Programa en JavaScript
- Sintaxi i Conceptes Bàsics de JavaScript
- Variables i Tipus de Dades
- Operadors Bàsics
- Conversió de Tipus i Comparacions
- El Projecte del Curs: Nómada Tasques
Mòdul 2: Estructures de Control
- Sentències Condicionals
- Bucles: for, while, do-while
- Sentències Switch
- Control del Flux: break, continue i Bucles Imbricats
- Gestió d'Errors amb try-catch
Mòdul 3: Funcions
- Definició i Crida de Funcions
- Expressions de Funció i Funcions Fletxa
- Paràmetres i Valors de Retorn
- Àmbit i Closures
- Hoisting i el Context d'Execució
- Funcions d'Ordre Superior
- Recursivitat
Mòdul 4: Objectes i Arrays
- Introducció als Objectes
- Mètodes d'Objecte i la Paraula Clau
this - Arrays: Conceptes Bàsics i Mètodes
- Iteració sobre Arrays
- Cercar, Ordenar i Agregar Dades: find, sort i reduce
- Desestructuració d'Arrays
- Desestructuració d'Objectes, Spread i Rest
- JSON i Còpies d'Objectes
Mòdul 5: Objectes i Funcions Avançades
- Prototips i Herència
- Classes i Programació Orientada a Objectes
- Encapsulació: Getters, Setters i Camps Privats
- Mòduls i Importació/Exportació
- JavaScript Asíncron: Callbacks
- Promeses i Async/Await
- El Bucle d'Esdeveniments i la Cua de Microtasques
- Iteradors i Generadors
Mòdul 6: El Model d'Objectes del Document (DOM)
- Introducció al DOM
- Selecció i Manipulació d'Elements del DOM
- Gestió d'Esdeveniments
- Propagació, Delegació i Esdeveniments Personalitzats
- Creació i Eliminació d'Elements del DOM
- Renderitzat de Llistes i Plantilles HTML
- Gestió i Validació de Formularis
Mòdul 7: APIs del Navegador i Temes Avançats
- Emmagatzematge Local i de Sessió
- Fetch API i AJAX
- Peticions Robustes: Errors, Timeouts i AbortController
- WebSockets
- Service Workers i Aplicacions Web Progressives (PWAs)
- APIs del Navegador Essencials
- Introducció a WebAssembly
Mòdul 8: Proves i Depuració
- Depuració de JavaScript
- Qualitat de Codi: ESLint, Prettier i Convencions
- Proves Unitàries amb Jest
- Dobles de Prova: Mocks, Stubs i Spies
- Proves d'Integració
- Proves d'Extrem a Extrem amb Cypress
Mòdul 9: Rendiment i Optimització
- Mesurar Abans d'Optimitzar: DevTools i Web Vitals
- Optimització del Rendiment de JavaScript
- Gestió de Memòria
- Manipulació Eficient del DOM
- Càrrega Diferida i Divisió de Codi
Mòdul 10: Frameworks i Llibreries de JavaScript
- Per Què Existeixen els Frameworks
- Introducció a React
- Gestió d'Estat amb Redux
- Conceptes Bàsics de Vue.js
- Conceptes Bàsics d'Angular
- Triar el Framework Adequat
