Tot el codi que has escrit en aquest mòdul assumeix que les dades són correctes: que horesEstimades és un nombre, que prioritat val 'alta', 'mitjana' o 'baixa', que la data límit té format ISO. Tan bon punt Nómada Tasques tingui un formulari, aquesta suposició es trencarà el primer dia: l'Iván escriurà "dos" on hi anaven les hores, algú deixarà el títol en blanc i una altra persona hi posarà una data de l'any passat. Sense protecció, el programa fa una de dues coses, totes dues dolentes: produeix un NaN silenciós que contamina tots els càlculs posteriors, o s'atura en sec amb un missatge que la Marta no entén. En aquesta lliçó aprendràs a detectar aquestes situacions, a interrompre l'execució amb un error propi i un missatge útil, i a capturar-lo per decidir què fer sense que l'aplicació sencera s'ensorri.

Contingut

  1. Errors de sintaxi i excepcions: no són el mateix
  2. Què passa quan ningú no captura una excepció
  3. try i catch
  4. finally i el flux complet
  5. L'objecte Error: name, message i stack
  6. Els tipus d'error incorporats
  7. throw: llançar els teus propis errors
  8. Per què es llança un Error i no una cadena
  9. Errors personalitzats: la recepta breu
  10. Validar entrades i fallar aviat
  11. Què NO cal capturar: silenciar errors és un antipatró
  12. Els errors asíncrons són una altra història
  13. Cas pràctic: acceptar tasques noves del formulari
  14. Errors Habituals i Consells
  15. Exercicis
  16. Conclusió

  1. Errors de sintaxi i excepcions: no són el mateix

Quan a la lliçó 01-03 vas aprendre a llegir un error de la consola, no vam distingir entre dues coses molt diferents. Ara sí que cal.

Error de sintaxi Excepció (error en execució)
Quan apareix En llegir el codi, abans d'executar res Durant l'execució, en una línia concreta
Causa El codi no és JavaScript vàlid Les dades o l'estat no són els esperats
Exemple const x = ; null.titol
Quant codi s'executa Gens del fitxer afectat Tot fins a la línia que falla
Es pot capturar? No, amb try/catch normal
Com s'arregla Corregint el codi Validant dades o capturant

Un error de sintaxi és una errada del programador, es detecta abans d'executar i no té sentit "gestionar-lo": es corregeix. Una excepció és una situació anòmala que passa mentre el programa funciona, gairebé sempre perquè les dades no eren les que esperaves. Aquesta és la que es gestiona.

flowchart TD
    A["El motor llegeix el fitxer"] --> B{"Sintaxi vàlida?"}
    B -->|no| C["SyntaxError<br/>No s'executa RES"]
    B -->|sí| D["Comença l'execució"]
    D --> E{"Situació anòmala<br/>en alguna línia?"}
    E -->|no| F["El programa acaba bé"]
    E -->|sí| G["Es llança una excepció"]
    G --> H{"Hi ha un try/catch<br/>al voltant?"}
    H -->|sí| I["catch decideix què fer<br/>el programa continua"]
    H -->|no| J["El programa s'atura"]

  1. Què passa quan ningú no captura una excepció

Quan es llança una excepció i ningú no la recull, l'execució s'atura allà mateix. Res del que venia després no s'executa.

const responsable = null;

console.log('Abans');
console.log(responsable.length);   // ✗ TypeError
console.log('Després');            // ← no s'executa mai

Sortida:

Abans
Uncaught TypeError: Cannot read properties of null (reading 'length')

La paraula clau és Uncaught: significa "ningú no ha capturat això". A Node.js el procés acaba amb codi d'error; al navegador s'atura el bloc de codi actual, encara que la resta de la pàgina continua viva i els botons que ja tenien escoltadors continuaran responent.

Aquest comportament —aturar-se— és deliberat i correcte. Si el programa continués endavant amb dades trencades, produiria resultats falsos que ningú no detectaria. És preferible aturar-se al punt exacte de la fallada.

El que aporta try/catch no és evitar l'aturada: és decidir què s'atura. Que una tasca del formulari estigui malament no hauria d'impedir processar les altres quatre.

  1. try i catch

L'estructura bàsica té dos blocs:

try {
  // Codi que pot fallar
} catch (error) {
  // Què cal fer si ha fallat
}

Amb un exemple del projecte: les dades d'una tasca arriben com a text JSON des de l'emmagatzematge del navegador i poden estar corruptes.

const textDesat = '{ això no és JSON vàlid }';

try {
  const dades = JSON.parse(textDesat);
  console.log('Tasques recuperades:', dades);
} catch (error) {
  console.error('No ha estat possible llegir les tasques desades.');
  console.error('Motiu tècnic:', error.message);
}

console.log('El programa continua.');

Sortida:

No ha estat possible llegir les tasques desades.
Motiu tècnic: Expected property name or '}' in JSON at position 2
El programa continua.

L'essencial:

  • El bloc try s'executa normalment fins que alguna cosa falla. Si no falla res, el catch se salta del tot.
  • Quan alguna cosa falla, l'execució salta al catch immediatament. La resta del try no s'executa: a l'exemple, el console.log('Tasques recuperades...') no arriba a córrer mai.
  • error és un paràmetre que rep l'objecte que descriu la fallada. Li pots posar el nom que vulguis; error, err i e són els habituals.
  • El programa continua després del catch. Aquesta és tota la màgia.

Si no necessites l'objecte d'error, JavaScript modern permet ometre el parèntesi:

try {
  JSON.parse(textDesat);
} catch {
  console.warn('Dades corruptes: es comença amb un tauler buit.');
}

Mantén el try tan petit com puguis. Embolcallar cinquanta línies en un sol try significa que qualsevol d'elles pot haver fallat i el catch no sap quina. Embolcalla només l'operació que de debò pot fallar.

  1. finally i el flux complet

finally és un tercer bloc opcional que s'executa sempre: hagi fallat alguna cosa o no, s'hagi capturat o no.

let tasquesProcessades = 0;

try {
  console.log('1. Obrint el tauler...');
  throw new Error('El magatzem no respon');
  console.log('2. Aquesta línia no arriba a executar-se');
} catch (error) {
  console.error(`3. Capturat: ${error.message}`);
} finally {
  console.log('4. Tancant el tauler (passi el que passi)');
}

console.log('5. El programa continua');

Sortida:

1. Obrint el tauler...
3. Capturat: El magatzem no respon
4. Tancant el tauler (passi el que passi)
5. El programa continua
flowchart TD
    A["Entra al try"] --> B{"Falla alguna cosa?"}
    B -->|no| C["Acaba el try complet"]
    B -->|sí| D["Salta al catch<br/>La resta del try es descarta"]
    C --> E["finally"]
    D --> E
    E --> F["Continua després del bloc"]

finally serveix per a les feines de neteja que han de passar sí o sí: tancar una connexió, amagar un indicador de càrrega, desbloquejar un botó. A Nómada Tasques el seu cas típic serà aquest, ja al Mòdul 6:

// Esquema del que faràs amb el DOM
try {
  // desar la tasca
} catch (error) {
  // mostrar el missatge d'error a l'usuari
} finally {
  // tornar a habilitar el botó "Desa", falli o no
}

Sense finally hauries de repetir aquesta línia al try i al catch. Amb ell s'escriu una vegada i s'executa segur.

Detall important: finally s'executa fins i tot si el catch torna a llançar l'error. I també s'executa si no hi ha catch en absolut: try/finally sense catch és una combinació legal i útil quan vols netejar però deixar que l'error pugi.

  1. L'objecte Error: name, message i stack

El que rep el catch és un objecte amb tres propietats fonamentals:

Propietat Què conté Exemple
name El tipus d'error 'TypeError'
message La descripció llegible 'Cannot read properties of null'
stack La pila de crides: on ha passat i com s'hi ha arribat Text multilínia amb fitxers i números de línia
try {
  const tasca = null;
  console.log(tasca.titol);
} catch (error) {
  console.log('Tipus:    ', error.name);       // TypeError
  console.log('Missatge: ', error.message);    // Cannot read properties of null (reading 'titol')
  console.log('Traça:\n', error.stack);
}

name i message són els que faràs servir a diari: name per decidir què fer i message per explicar què ha passat.

stack és l'eina de diagnòstic. Conté el recorregut que va seguir l'execució fins al punt de la fallada, amb noms de fitxer i números de línia. Es llegeix de dalt a baix: la primera línia és on es va produir l'error i les següents, qui el va provocar. Al Mòdul 3, quan tinguis funcions cridant funcions, la pila es tornarà realment informativa; avui té una o dues línies.

Registra l'error complet, no només el missatge. console.error(error) a la consola del navegador mostra l'objecte sencer amb la seva traça plegable; console.error(error.message) llença aquesta informació a les escombraries i et deixa sense saber on ha passat.

  1. Els tipus d'error incorporats

JavaScript defineix diversos tipus d'error. Tots comparteixen name, message i stack, i es diferencien en la mena de problema que representen.

Tipus Quan es llança Exemple típic al projecte
TypeError Un valor no és del tipus esperat, o s'accedeix a una propietat de null/undefined tasca.titol quan tasca és null
ReferenceError Es fa servir una variable que no existeix o encara no està inicialitzada Escriure prioritt en lloc de prioritat
RangeError Un valor numèric és fora del rang admès new Array(-1); també el faràs servir tu per a les hores fora de 0–40
SyntaxError El codi no és JavaScript vàlid Es detecta abans d'executar; també el llança JSON.parse amb text corrupte
URIError Ús incorrecte de les funcions de codificació d'URL decodeURIComponent('%')
EvalError Pràcticament en desús

Els tres primers són els que veuràs el 95 % del temps. Comprova'ls:

// TypeError
try { null.titol; } catch (e) { console.log(e.name); }              // TypeError

// ReferenceError
try { console.log(prioritt); } catch (e) { console.log(e.name); }   // ReferenceError

// RangeError
try { new Array(-1); } catch (e) { console.log(e.name); }           // RangeError

// SyntaxError en temps d'execució, via JSON.parse
try { JSON.parse('{'); } catch (e) { console.log(e.name); }         // SyntaxError

Conèixer el tipus permet reaccionar de manera diferent a cadascun:

try {
  JSON.parse(textDesat);
} catch (error) {
  if (error.name === 'SyntaxError') {
    console.warn('Les dades desades estan corruptes. Comencem de zero.');
  } else {
    console.error('Fallada inesperada en llegir el tauler:', error);
  }
}

Existeix una manera més idiomàtica de fer aquesta comprovació —error instanceof SyntaxError—, però instanceof pertany al món dels prototips i les classes, que s'estudia al Mòdul 5. Comparar error.name amb un text funciona perfectament i és el que farem servir de moment.

  1. throw: llançar els teus propis errors

Fins aquí has capturat errors que llançava JavaScript. L'altra meitat del mecanisme és llançar-los tu, amb throw, quan detectes que una regla de negoci no es compleix.

const horesEstimades = 52;

try {
  if (horesEstimades > 40) {
    throw new RangeError(`R3 incomplerta: ${horesEstimades} h superen el màxim de 40.`);
  }
  console.log('Tasca acceptada.');
} catch (error) {
  console.error(`[${error.name}] ${error.message}`);
}
// [RangeError] R3 incomplerta: 52 h superen el màxim de 40.

Què fa throw, exactament:

  1. Atura l'execució en aquell punt. Com el break de la lliçó anterior, però molt més radical: no salta a la iteració següent, abandona tot el bloc try.
  2. Busca el catch més proper que embolcalli aquella línia.
  3. Si no n'hi ha cap, l'error queda Uncaught i el programa s'atura.

new Error(...) crea l'objecte d'error. El text que li passes es converteix en el seu message. Pots fer servir Error genèric o el tipus que descrigui millor el problema: RangeError per a valors fora de rang, TypeError per a tipus incorrectes.

Com escriure un bon missatge d'error. El missatge el llegirà algú que intenta arreglar alguna cosa, així que ha de contenir tres coses:

Ingredient Malament
Què ha fallat 'Error' 'horesEstimades fora de rang'
Quin valor ho ha causat 'Valor invàlid' 'valor rebut: 52'
Què s'esperava 'ha de ser entre 0 i 40'

Tot junt:

throw new RangeError(`horesEstimades fora de rang: s'ha rebut ${horesEstimades}, ha de ser entre 0 i 40.`);

Aquest missatge s'explica sol, sense obrir el codi.

  1. Per què es llança un Error i no una cadena

throw accepta qualsevol valor. Això és legal:

// ✗ No ho facis
throw 'Les hores no són vàlides';

I és una mala idea per tres motius concrets:

1. Perds la traça. Una cadena no té stack, així que al catch no hi ha manera de saber en quina línia es va originar el problema. Amb projectes de més d'un fitxer, això converteix la depuració en endevinalla.

2. Perds el tipus. error.name és undefined, així que no pots reaccionar de manera diferent segons la mena de problema.

3. Trenques el codi que captura. Un catch ben escrit fa error.message. Si li arriba una cadena, error.message val undefined i el missatge es perd. Pitjor: si algú llança null, error.message provoca al seu torn un TypeError dins del catch.

Compara-ho:

try {
  throw 'alguna cosa va malament';
} catch (error) {
  console.log(error.name);      // undefined
  console.log(error.message);   // undefined
  console.log(error);           // alguna cosa va malament
}

try {
  throw new Error('alguna cosa va malament');
} catch (error) {
  console.log(error.name);      // Error
  console.log(error.message);   // alguna cosa va malament
  console.log(error.stack);     // Error: alguna cosa va malament\n    at ...
}

Norma sense excepcions: llança sempre un objecte Error o un dels seus tipus derivats.

  1. Errors personalitzats: la recepta breu

Quan vulguis distingir els teus errors de negoci dels del llenguatge, pots crear un tipus propi. La recepta és aquesta, i de moment n'hi ha prou amb copiar-la:

class ErrorDeValidacio extends Error {
  constructor(missatge, camp) {
    super(missatge);             // passa el missatge a l'Error base
    this.name = 'ErrorDeValidacio';
    this.camp = camp;            // dada extra: quin camp ha fallat
  }
}

Es fa servir igual que qualsevol altre error, amb l'avantatge de portar informació addicional:

const titol = '   ';

try {
  if (titol.trim() === '') {
    throw new ErrorDeValidacio('El títol no pot estar buit (R2).', 'titol');
  }
} catch (error) {
  if (error.name === 'ErrorDeValidacio') {
    console.error(`Camp "${error.camp}": ${error.message}`);
  } else {
    console.error('Error inesperat:', error);
  }
}
// Camp "titol": El títol no pot estar buit (R2).

L'avantatge pràctic és doble: pots filtrar al catch els errors que saps tractar i deixar passar els altres, i pots adjuntar dades —el camp, el valor rebut, l'id de la tasca— que el formulari farà servir per marcar en vermell el requadre correcte.

Què signifiquen exactament class, extends, constructor, super i this s'explica a Classes i POO. Aquí és només una plantilla que pots reutilitzar canviant-ne el nom.

  1. Validar entrades i fallar aviat

El principi s'anomena fail fast: comprovar les dades en el moment en què entren al sistema i rebutjar-les allà mateix, en lloc de deixar-les avançar i descobrir el problema tres capes més endins.

La diferència es veu millor amb un exemple. Sense validació:

const horesText = 'dotze';                    // arriba del formulari
const hores = Number(horesText);              // NaN

const totalBacklog = 45 + hores;              // NaN
const mitjana = totalBacklog / 6;             // NaN
console.log(`Mitjana per tasca: ${mitjana} h`); // Mitjana per tasca: NaN h

El NaN no provoca cap error: es propaga en silenci per tots els càlculs i apareix al final, en un informe, sense cap pista de quina va ser la tasca culpable. Aquest és exactament l'escenari contra el qual et va prevenir la lliçó 01-07.

Amb validació a l'entrada:

const horesText = 'dotze';

try {
  const hores = Number(horesText);

  if (Number.isNaN(hores)) {
    throw new TypeError(`horesEstimades ha de ser un nombre; s'ha rebut "${horesText}".`);
  }
  if (hores <= 0 || hores > 40) {
    throw new RangeError(`horesEstimades fora de rang: ${hores}. Ha de ser entre 0 i 40.`);
  }

  console.log(`Hores acceptades: ${hores}`);
} catch (error) {
  console.error(`Tasca rebutjada — [${error.name}] ${error.message}`);
}
// Tasca rebutjada — [TypeError] horesEstimades ha de ser un nombre; s'ha rebut "dotze".

La fallada es detecta al punt exacte on va entrar la dada dolenta, el missatge anomena el camp i el valor rebut, i cap càlcul posterior no arriba a contaminar-se.

Fixa't que es fa servir Number.isNaN(hores) i no isNaN(hores): la distinció de la lliçó 01-07 continua sent la correcta, perquè isNaN converteix abans de comprovar i dona falsos positius.

L'ordre de les validacions importa. Primer el tipus, després el rang. Comprovar hores > 40 quan hores és NaN dona false, i la validació passaria sense detectar res: totes les comparacions amb NaN són falses.

  1. Què NO cal capturar: silenciar errors és un antipatró

try/catch és una eina poderosa, i com tota eina poderosa es fa servir malament amb facilitat. Aquests són els tres abusos que cal evitar.

1. El catch buit. El pitjor de tots.

// ✗ MAI
try {
  processarTasca();
} catch (error) { }

Això no gestiona l'error: l'oculta. El programa continua amb un estat incorrecte, sense deixar ni rastre a la consola, i la fallada emergirà més tard en un altre lloc completament diferent. Depurar això pot costar hores. Si de debò un error concret és esperable i ignorable, escriu-ho explícitament:

} catch (error) {
  // És normal que no hi hagi dades desades la primera vegada: comencem buits.
  console.info('Sense tauler previ; es crea un de nou.');
}

2. Capturar errors de programació. Un TypeError per escriure malament el nom d'una variable és un bug, no una situació excepcional. Embolcallar-lo en un try/catch no l'arregla: l'amaga. Els bugs es corregeixen; només es capturen les situacions que el programa no controla.

Situació try/catch? Per què
Dades que escriu un usuari ✓ Sí No les controles
Un fitxer o una API que pot fallar ✓ Sí Depèn de l'exterior
JSON.parse de dades desades ✓ Sí Poden estar corruptes
Un nom de variable mal escrit ✗ No És un bug: corregeix-lo
Un array que saps que existeix ✗ No Si falla, la teva lògica és incorrecta

3. El try gegant. Embolcallar un bloc de cent línies fa que el catch no pugui saber què ha fallat ni reaccionar de manera sensata. Embolcalla l'operació arriscada, no el programa sencer.

I un patró que sí que és correcte: capturar, enriquir i tornar a llançar.

try {
  JSON.parse(textDesat);
} catch (error) {
  console.error('Fallada en llegir el tauler desat:', error.message);
  throw error;      // no el silencio: hi afegeixo context i deixo que pugi
}

És el que faràs en capes intermèdies: registrar informació útil sense decidir tu què fer amb el problema.

  1. Els errors asíncrons són una altra història

Un advertiment important perquè no t'agafi per sorpresa més endavant: try/catch només captura errors del codi que s'executa ara mateix, dins del bloc try. Si dins del try inicies una operació que acabarà més tard —una petició a un servidor, un temporitzador—, el try haurà acabat fa estona quan aquella operació falli, i el catch no se n'assabentarà.

// ✗ El catch NO captura res d'això
try {
  setTimeout(function () {
    throw new Error('Fallada tardana');
  }, 1000);
} catch (error) {
  console.error('Això no arriba a executar-se mai');
}

L'error es llança un segon després, quan el try/catch ja no existeix, i queda com a Uncaught.

La gestió d'errors en codi asíncron té les seves pròpies eines: .catch() a les promeses i try/catch combinat amb await, que sí que funciona. Tot això s'estudia a Promeses i Async/Await. Per ara queda't amb la regla: try/catch protegeix el que és síncron.

  1. Cas pràctic: acceptar tasques noves del formulari

Tanquem el mòdul ajuntant tot el que hem après. La Marta ha recollit cinc sol·licituds de tasca en un formulari i cal processar-les: acceptar les vàlides, rebutjar les que incompleixin les regles i —això és el més important— que una sol·licitud dolenta no impedeixi processar les següents.

Les dades arriben com a text, tal com surten d'un formulari HTML.

const AVUI = '2026-09-20';
const EQUIP = ['Marta', 'Iván', 'Lucía'];

const solTitols = [
  'Canviar la làmpada de la sala polivalent',
  '   ',
  'Comprar tinta blanca de serigrafia',
  'Renovar la fulla del torn',
  'Arxivar les factures del trimestre'
];
const solResponsables = ['Lucía', 'Marta', 'Iván', 'Iván', 'Marta'];
const solPrioritats   = ['mitjana', 'alta', 'mitjana', 'urgent', 'baixa'];
const solHores        = ['4', '3', 'dos', '10', '3'];
const solDates        = ['2026-10-20', '2026-10-01', '2026-10-01', '2026-10-05', '2026-08-01'];

let acceptades = 0;
let rebutjades = 0;
let horesAcceptades = 0;

for (let i = 0; i < solTitols.length; i++) {
  try {
    // R2 — títol no buit i de longitud raonable
    const titol = solTitols[i].trim();
    if (titol === '') {
      throw new Error('R2: el títol no pot estar buit.');
    }
    if (titol.length > 100) {
      throw new RangeError(`R2: el títol té ${titol.length} caràcters; el màxim és 100.`);
    }

    // R8 — responsable nul o de l'equip
    const responsable = solResponsables[i];
    let esDelEquip = false;
    for (let p = 0; p < EQUIP.length; p++) {
      if (EQUIP[p] === responsable) {
        esDelEquip = true;
        break;
      }
    }
    if (responsable !== null && !esDelEquip) {
      throw new Error(`R8: "${responsable}" no pertany a l'equip de Taller Nómada.`);
    }

    // Prioritat dins del conjunt tancat
    const prioritat = solPrioritats[i];
    if (prioritat !== 'alta' && prioritat !== 'mitjana' && prioritat !== 'baixa') {
      throw new Error(`Prioritat no vàlida: "${prioritat}". Feu servir alta, mitjana o baixa.`);
    }

    // R3 — hores: primer el tipus, després el rang
    const hores = Number(solHores[i]);
    if (Number.isNaN(hores)) {
      throw new TypeError(`R3: horesEstimades ha de ser un nombre; s'ha rebut "${solHores[i]}".`);
    }
    if (hores <= 0 || hores > 40) {
      throw new RangeError(`R3: ${hores} h fora de rang. Ha de ser entre 0 i 40.`);
    }

    // R4 — la data límit no pot ser al passat
    const data = solDates[i];
    if (data < AVUI) {
      throw new RangeError(`R4: la data límit ${data} és anterior a avui (${AVUI}).`);
    }

    // Si hem arribat aquí, la sol·licitud és vàlida
    acceptades++;
    horesAcceptades += hores;
    console.log(`✓ Acceptada: ${titol} · ${responsable} · ${prioritat} · ${hores} h · ${data}`);

  } catch (error) {
    rebutjades++;
    console.error(`✗ Sol·licitud ${i + 1} rebutjada — [${error.name}] ${error.message}`);
  }
}

console.log('---');
console.log(`Acceptades: ${acceptades}`);
console.log(`Rebutjades: ${rebutjades}`);
console.log(`Hores afegides al backlog: ${horesAcceptades} h`);

Sortida:

✓ Acceptada: Canviar la làmpada de la sala polivalent · Lucía · mitjana · 4 h · 2026-10-20
✗ Sol·licitud 2 rebutjada — [Error] R2: el títol no pot estar buit.
✗ Sol·licitud 3 rebutjada — [TypeError] R3: horesEstimades ha de ser un nombre; s'ha rebut "dos".
✗ Sol·licitud 4 rebutjada — [Error] Prioritat no vàlida: "urgent". Feu servir alta, mitjana o baixa.
✗ Sol·licitud 5 rebutjada — [RangeError] R4: la data límit 2026-08-01 és anterior a avui (2026-09-20).
---
Acceptades: 1
Rebutjades: 4
Hores afegides al backlog: 4 h

Repassa les decisions de disseny, perquè són les que aplicaràs a tot el projecte:

  • El try és dins del bucle, no fora. Aquesta és la clau de tot l'exemple. Amb el try embolcallant el bucle sencer, la primera sol·licitud invàlida hauria avortat el procés i les tres següents no s'haurien revisat mai. Posant-lo a dins, cada sol·licitud es processa de manera independent i una dada dolenta només afecta la seva pròpia fila.
  • throw funciona com una sortida primerenca perfecta. És la clàusula de guarda de la lliçó 02-01, aquesta vegada amb una sortida real: tan bon punt una regla falla, la resta de validacions d'aquella sol·licitud es descarten. No hi ha imbricació, no hi ha variable error per arrossegar, no hi ha else.
  • Cada throw tria el seu tipus d'error. TypeError per a un tipus equivocat, RangeError per a valors fora de rang, Error genèric per a la resta de regles de negoci. Amb això, el catch podria reaccionar diferent a cada família.
  • L'ordre de les validacions és deliberat. El tipus abans que el rang, sempre. I les comprovacions barates abans que les cares, com vas aprendre a la lliçó 02-04.
  • Hi apareixen les quatre estructures del mòdul. El for extern que recorre les sol·licituds, el for intern amb break que busca el responsable a l'equip, els if de cada validació i el try/catch que ho embolcalla tot. Aquest és el mòdul complet funcionant plegat.

I ara fixa't en una cosa incòmoda: el bloc de validació té quaranta línies i només serveix per a això. Si demà cal validar una tasca editada en lloc d'una de nova, cal copiar-lo sencer.

Errors Habituals i Consells

El catch buit. Ja ho has vist, però val la pena repetir-ho: és el pitjor error d'aquesta lliçó. Un catch que no registra res converteix una fallada localitzada en un misteri.

Llançar cadenes en lloc d'objectes Error. Perds name i stack, i trenques qualsevol catch que esperi un error de debò.

Posar el try fora del bucle quan havia d'anar a dins. Una entrada dolenta avorta el procés complet. Pregunta't sempre: una fallada aquí ha d'aturar-ho tot o només aquesta iteració?

Fer servir try/catch per al que resol un if. Comprovar si un valor és negatiu no requereix excepcions: requereix una condició. Les excepcions són per a allò excepcional; si l'"error" passa en el flux normal, és un cas, no una excepció.

Esperar que try/catch capturi errors asíncrons. No ho fa. setTimeout, fetch i les promeses necessiten un altre tractament, que veuràs al Mòdul 5.

Comprovar el rang abans que el tipus. Amb NaN totes les comparacions donen false, així que un valor no numèric passa qualsevol validació de rang sense detectar-se.

Registrar només error.message. Perds la traça. Fes servir console.error(error) quan estiguis depurant.

Consell: escriu el missatge pensant en qui el llegirà a les tres de la matinada. Què ha fallat, quin valor ho ha causat i què s'esperava. Els tres ingredients, sempre.

Consell: inclou l'identificador de la regla al missatge. R3: al principi del text connecta l'error amb l'especificació del projecte i estalvia molt de temps.

Consell: prova les teves validacions amb dades dolentes a propòsit. Escriu deliberadament 'dos', -5, '' i null i comprova que cadascun produeix el missatge que esperaves. És el germen de les proves unitàries del Mòdul 8.

Exercicis

Exercici 1 — Lectura robusta del tauler desat

Escriu un bloc try/catch/finally que intenti llegir amb JSON.parse el contingut de la variable desat. Si el text és vàlid, mostra un missatge d'èxit; si està corrupte, avisa que es comença amb un tauler buit. Al finally, imprimeix sempre "Tauler a punt". Prova-ho amb '[1,2,3]' i amb '{ trencat', i explica per què fer servir if en lloc de try/catch no seria viable aquí.

Exercici 2 — Validador de transicions que llança errors

Reescriu la validació de canvi d'estat de la lliçó 02-01 fent servir throw en lloc de les variables permes i motiu. Ha de llançar un Error amb missatge descriptiu quan l'estat nou no existeixi, quan sigui igual a l'actual o quan la transició no estigui permesa per la R6. Embolcalla-ho en un try/catch i prova-ho amb pendent → feta i amb en-curs → feta.

Exercici 3 — Errors personalitzats amb el camp culpable

Fent servir la recepta de la secció 9, crea ErrorDeValidacio amb les propietats camp i valorRebut. Valida una tasca amb titol = '', hores = '35' i prioritat = 'baixa', i fes que el catch distingeixi entre els teus errors de validació —mostrant el camp culpable— i qualsevol altre error inesperat.

Solucions

Exercici 1

const desat = '{ trencat';

try {
  const tauler = JSON.parse(desat);
  console.log(`Tauler recuperat amb ${tauler.length} element/s.`);
} catch (error) {
  console.warn(`Dades desades il·legibles (${error.name}). Es comença amb un tauler buit.`);
} finally {
  console.log('Tauler a punt.');
}

Amb '[1,2,3]':

Tauler recuperat amb 3 element/s.
Tauler a punt.

Amb '{ trencat':

⚠ Dades desades il·legibles (SyntaxError). Es comença amb un tauler buit.
Tauler a punt.

Per què no serveix un if: per saber si un text és JSON vàlid caldria analitzar-lo caràcter a caràcter, és a dir, reimplementar JSON.parse sencer. No existeix cap comprovació prèvia raonable. Aquest és el cas arquetípic de try/catch: l'única manera de saber si l'operació funciona és intentar-la. Quan ho puguis comprovar abans amb una condició, fes servir la condició; quan no, captura.

El finally garanteix que l'aplicació continua pels dos camins amb el mateix missatge, sense duplicar-lo als dos blocs.

Exercici 2

const estatActual = 'pendent';
const estatNou = 'feta';

try {
  const esEstatValid =
    estatNou === 'pendent' || estatNou === 'en-curs' || estatNou === 'feta';

  if (!esEstatValid) {
    throw new TypeError(`"${estatNou}" no és un estat del sistema.`);
  }
  if (estatActual === estatNou) {
    throw new Error(`La tasca ja està en estat "${estatActual}".`);
  }

  const transicioValida =
    (estatActual === 'pendent' && estatNou === 'en-curs') ||
    (estatActual === 'en-curs' && estatNou === 'feta') ||
    (estatActual === 'en-curs' && estatNou === 'pendent') ||
    (estatActual === 'feta'    && estatNou === 'en-curs');

  if (!transicioValida) {
    throw new Error(`R6: transició no permesa ${estatActual} → ${estatNou}.`);
  }

  console.log(`✓ Estat actualitzat a "${estatNou}".`);
} catch (error) {
  console.error(`✗ [${error.name}] ${error.message}`);
}

Amb pendent → feta:

✗ [Error] R6: transició no permesa pendent → feta.

Amb en-curs → feta:

✓ Estat actualitzat a "feta".

Compara aquesta versió amb la de la lliçó 02-01: allà calien les variables permes i motiu, i cada branca havia d'assignar-les totes dues. Aquí cada guarda o llança o deixa passar, i el camí feliç —el console.log final— és al mateix nivell d'indentació que tota la resta.

Fixa't també que les quatre transicions vàlides s'han agrupat en una sola expressió booleana amb parèntesis, aplicant la norma de la lliçó 02-01: quan es barregen && i ||, els parèntesis hi van encara que la precedència ja sigui correcta.

Exercici 3

class ErrorDeValidacio extends Error {
  constructor(missatge, camp, valorRebut) {
    super(missatge);
    this.name = 'ErrorDeValidacio';
    this.camp = camp;
    this.valorRebut = valorRebut;
  }
}

const titol = '';
const horesText = '35';
const prioritat = 'baixa';

try {
  if (titol.trim() === '') {
    throw new ErrorDeValidacio('R2: el títol és obligatori.', 'titol', titol);
  }

  const hores = Number(horesText);
  if (Number.isNaN(hores)) {
    throw new ErrorDeValidacio('R3: les hores han de ser un nombre.', 'horesEstimades', horesText);
  }
  if (hores <= 0 || hores > 40) {
    throw new ErrorDeValidacio('R3: les hores han de ser entre 0 i 40.', 'horesEstimades', hores);
  }

  if (prioritat !== 'alta' && prioritat !== 'mitjana' && prioritat !== 'baixa') {
    throw new ErrorDeValidacio('Prioritat no reconeguda.', 'prioritat', prioritat);
  }

  console.log('✓ Tasca vàlida.');
} catch (error) {
  if (error.name === 'ErrorDeValidacio') {
    console.error(`✗ Camp "${error.camp}" — ${error.message} (s'ha rebut: ${JSON.stringify(error.valorRebut)})`);
  } else {
    console.error('✗ Error inesperat, això és un bug:', error);
  }
}
// ✗ Camp "titol" — R2: el títol és obligatori. (s'ha rebut: "")

Tres coses que fan valuós aquest patró:

  1. error.camp permet que la interfície reaccioni. Al Mòdul 6, aquesta dada marcarà en vermell exactament el requadre del formulari que cal corregir, en lloc de mostrar un avís genèric.
  2. JSON.stringify(error.valorRebut) mostra el valor entre cometes, cosa que distingeix el text buit "" d'un espai " " o de null. Un console.log normal els imprimiria tots tres de manera indistingible.
  3. La branca else no silencia res. Els errors que no són de validació —un TypeError per un bug teu— es registren complets, amb la seva traça. Filtrar el que saps tractar i deixar visible la resta és la diferència entre gestionar errors i amagar-los.

Canvia titol per 'Renovar el torn' i veuràs com la validació avança fins al final i imprimeix ✓ Tasca vàlida., perquè 35 h és dins del rang i 'baixa' és una prioritat correcta.

Conclusió

Amb aquesta lliçó tanques el Mòdul 2. Ja distingeixes un error de sintaxi —una errada del codi, que es corregeix— d'una excepció —una situació anòmala en execució, que es gestiona—. Saps que una excepció sense capturar atura el programa, i que try/catch no evita l'aturada sinó que et deixa decidir què s'atura: a l'exemple del formulari, una sol·licitud invàlida ja no impedeix processar les altres quatre. Coneixes finally per a la neteja que ha de passar passi el que passi, l'objecte Error amb el seu name, el seu message i el seu stack, i els tipus incorporats que et permeten reaccionar de manera diferent a cada mena de problema.

Saps llançar els teus propis errors amb throw, sempre amb un objecte Error i mai amb una cadena, i escriure missatges que diguin què ha fallat, quin valor ho ha causat i què s'esperava. Tens la recepta dels errors personalitzats per adjuntar el camp culpable. I —tan important com l'anterior— saps el que no cal capturar: un catch buit no gestiona un error, l'amaga; els bugs es corregeixen en lloc d'embolcallar-los; i els errors asíncrons necessiten les eines del Mòdul 5.

Mira enrere i comprova el que has guanyat en aquestes cinc lliçons. El teu codi decideix amb if/else if i amb switch, repeteix amb for, while i do...while, recorre el backlog complet acumulant, comptant, buscant extrems i creuant llistes amb bucles imbricats, talla en el moment just amb break i continue, i es defensa de les dades dolentes amb validacions que fallen aviat i amb missatges útils. Això ja és un programa de debò.

I també has anat acumulant una queixa, lliçó rere lliçó. La cadena que converteix prioritat en un pes l'has escrita quatre vegades. La validació de transicions apareix en dues lliçons gairebé idèntica. El bloc de quaranta línies que valida una tasca nova no es pot reutilitzar per validar una tasca editada sense copiar-lo sencer. I quan vas voler sortir de dos bucles alhora, la solució més neta va resultar ser una que encara no podies escriure. Tot apunta al mateix lloc: necessites poder donar un nom a un tros de lògica, desar-lo i fer-lo servir tantes vegades com vulguis, amb dades diferents cada vegada. Això és una funció, i és exactament el que comença al Mòdul 3: Funcions, amb Definició i Crida de Funcions. A partir d'aquí, validarTasca(), calcularUrgencia() i potCanviarEstat() deixaran de ser blocs copiats per convertir-se en peces amb nom propi que podràs combinar, provar i reutilitzar a tot Nómada Tasques.

Curs de JavaScript: De Principiant a Avançat

Mòdul 1: Introducció a JavaScript

Mòdul 2: Estructures de Control

Mòdul 3: Funcions

Mòdul 4: Objectes i Arrays

Mòdul 5: Objectes i Funcions Avançades

Mòdul 6: El Model d'Objectes del Document (DOM)

Mòdul 7: APIs del Navegador i Temes Avançats

Mòdul 8: Proves i Depuració

Mòdul 9: Rendiment i Optimització

Mòdul 10: Frameworks i Llibreries de JavaScript

Mòdul 11: Projecte Final

© Copyright 2026. Tots els drets reservats