Tutorial per manejar errors asíncrons amb try/catch en promeses

  • Comprendre el motor de JavaScript, la pila de trucades i les cues de tasques és clau per predir com i quan s'executen els manejadors d'errors en codi asíncron.
  • Les promeses introdueixen un “try/catch invisible” que captura excepcions i permet encadenar then y catch per gestionar errors de forma centralitzada i llegible.
  • la sintaxi async/await simplifica el maneig d'errors usant blocs try/catch clàssics i afavoreix una escriptura més seqüencial del codi asíncron.
  • Esdeveniments globals com unhandledrejection i patrons de composició correctes ajuden a evitar rebutjos no manejats ia dissenyar una estratègia robusta de logging i notificació derrors.

manejar errors asíncrons amb try/catch

Manejar bé els errors asíncrons en JavaScript marca la diferència entre una aplicació professional i una que es trenca a la mínima. Quan comences a fer servir Promises i async/await, el normal és estirar try/catch “perquè toca”, però sense una estratègia clara és fàcil acabar amb errors silenciosos, missatges poc útils per a l'usuari i advertiments de “Unhandled promise rejection” per tota la consola.

En aquest article veurem, amb calma i amb exemples pràctics, com estructurar un tutorial complet per manejar errors asíncrons amb try/catch en promeses, com funciona el motor de JavaScript per sota, quin paper juguen les cues de tasques i microtasques, i com combinar correctament .then(), .catch() y async/await per no deixar cap error solt, ni al navegador ni a Node.js.

Com funciona internament JavaScript i per què afecta el maneig derrors?

Abans d'entrar de ple al maneig d'errors amb promeses, convé entendre una mica el motor de JavaScript, la pila de trucades i l'entorn d'execució. Això explica perquè certs errors apareixen “després” o per què les promeses es comporten diferent dels callbacks clàssics.

Com aplicar pegats al codi amb la utilitat patch
Article relacionat:
Les millors aplicacions per aprendre a programar des del mòbil

Entorn d'execució, context i pila de trucades

JavaScript s'executa en un únic fil (almenys conceptualment), cosa que implica que el motor només pot estar fent una cosa alhora a la pila de trucades. Per no bloquejar tota la web mentre esperem a la xarxa, al disc o al DOM, els navegadors exposen Web APIs que permeten fer operacions asíncrones i que, més endavant, tornen el resultat mitjançant callbacks, promeses o async/await.

Tot el codi de JavaScript viu en algun context d'execució. Tenim el context global (el primer que es crea en carregar l'script o la pàgina) i, cada vegada que s'invoca una funció, es crea un nou context de funció amb les variables, la seva this i un enllaç al context exterior. Aquests contextos s'apilen a la call stack (pila de trucades) seguint una política LIFO: la darrera funció que entra és la primera que surt.

Quan truques a una funció com first(), aquesta es col·loca a la pila; si dins truca a second() i aquesta a third(), veuràs com la pila va afegint i traient contextos, i cada missatge per consola reflecteix l'ordre d'entrada i eixida de la pila. Eines com el depurador del navegador, els breakpoints o console.trace() permeten inspeccionar aquesta pila en temps real.

Cues de tasques i microtasques: per què les promeses s'executen “després”

El motor de JavaScript també treballa amb diverses cues de tasques. Les més rellevants aquí són la cua de macrotasques i la de microtasques. A la cua de macrotasques entren coses com setTimeout, setInterval o esdeveniments del DOM. A la cua de microtasques entren, entre d'altres, les callbacks de les promeses i els observadors de mutació del DOM.

El cicle d'esdeveniments funciona així: s'executa el codi síncron, després s'atén una macrotasca, després es buida la cua de microtasques (tots els pendents), i només llavors es procedeix a la següent macrotasca. Per això un then() o un catch() associat a una promesa es dispara abans que un setTimeout(..., 0), encara que totes dues semblin “immediates”.

Aquest detall és clau per entendre quan s'executen els manejadors d'errors de les promeses i per què un error que passa dins d'un .then() no rebenta el teu script a linstant, sinó que apareix en una fase posterior del cicle desdeveniments.

Callbacks, callback hell i el salt a Promises

Històricament, les operacions asíncrones en JavaScript es manejaven sempre amb devolucions de trucada. Una funció rebia una altra funció com a argument i l'executava quan la tasca acabava: peticions al servidor, temporitzadors, lectura de fitxers amb File API, operacions amb IndexedDB, etc.

Imagina una funció fetchData(callback) que truca internament a setTimeout per simular una petició de xarxa i, passats uns segons, invoca el callback amb les dades. Mentrestant, el programa continua la seva execució mostrant missatges com a “Data is being fetched…”. Això il·lustra que el callback s'executa després, quan l'esdeveniment arriba a la cua i el motor el processa.

Callback hell: quan els callbacks es tornen immanejables

Quan comences a encadenar operacions que depenen les unes de les altres, la cosa es complica. Per exemple, manegeu el clic d'un botó, feu una petició amb XMLHttpRequest (sense promeses), uses setTimeout per diferir el processament, llegeixes un fitxer d'un <input type="file">, i després guardes el resultat a indexedDB. Cadascuna d'aquestes APIs espera un callback diferent.

Si intentes coordinar-ho tot amb callbacks niats, acabes amb la famosa “piràmide de la perdició” o tornar a trucar l'infern: molta indentació, difícil de llegir, gairebé impossible de mantenir i un infern a l'hora de gestionar errors, perquè has de repetir la lògica d'error a cada nivell.

Aquest escenari va ser un dels grans motius per a la introducció de les promet en JavaScript: una forma més declarativa i manejable de representar operacions asíncrones i els seus possibles errors.

Promeses: estats, encadenament i maneig derrors

manejar errors asíncrons amb try/catch

Una Promise és un objecte que representa el resultat futur d'una operació asíncrona: es pot completar bé o fallar. Internament té tres estats: pendent, complert (resolt) o rebutjat. El constructor de Promise rep una funció “executora” que alhora rep dues funcions: resolve y reject.

Per exemple, podeu crear una promesa que, després d'un temps aleatori, truqueu a resolve() o reject() segons certa condició. Després, usant then(), defineixes què fer quan la promesa es resol, i amb catch() què fer quan es rebutja. Aquest patró et permet tornar un objecte immediatament (la promesa) que actuarà com a “contenidor” d'aquest resultat futur.

Consumint promeses: then, catch i encadenament

Les funcions modernes solen tornar directament una promesa. En lloc de passar callbacks d'èxit i error per separat, en reps una promise a la que li penges callbacks amb then() y catch(). Per exemple, una funció crearArchivoAudioAsync() podria tornar una promesa en lloc desperar callbacks al vell estil.

El veritable poder arriba amb el encadenament de promeses. el mètode then() torna sempre una nova promesa. Això vol dir que pots escriure coses com:

hazAlgo() seguit de .then(hazAquello), que alhora torna una altra promesa a la qual li encadenes .then(hazOtraCosa), i així successivament. Cada pas representa la finalització de l'anterior, i si qualsevol d'aquests callbacks torna una altra promesa, la cadena s'ajusta automàticament per esperar que aquesta nova promesa es resolgui.

Amb aquest patró, el que abans era una piràmide de callbacks niats es converteix en una cadena plana d'operacions asíncrones, molt més llegible. A més, els errors es poden manejar de forma centralitzada amb un únic .catch() al final de la cadena.

El “try/catch invisible” a les promeses

Un dels punts clau de les promeses és que l'executor intern i els manejadors (then, catch) porten incorporat un “try/catch invisible”. Per exemple, en utilitzar funcions fletxa i this en JavaScript, qualsevol excepció que es llenci dins duna promesa, o en el codi dun manejador, es tradueix automàticament en un rebuig de la promesa.

Així, el codi següent:

new Promise((resolve, reject) => { throw new Error("oops") })

és funcionalment equivalent a trucar explícitament a reject(new Error("oops")). El mateix passa als callbacks de .then(): si fas throw dins, no rebenta tot el teu script; en canvi, la promesa resultant passa a l'estat rebutjat i el control salta al següent manejador d'error de la cadena.

Maneig d'errors: catch, propagació i rellançat

el mètode .catch() captura qualsevol rebuig que es produeixi a la promesa anterior oa qualsevol de les que hi hagi per sobre a la cadena, sempre que l'error no hagi estat ja manejat. Pots col·locar un .catch() al final d'una cadena llarga de .then() per centralitzar el tractament d'errors, igual que tindries un únic try..catch embolicant diversos blocs de codi.

dins .catch() pots analitzar el tipus d'error (per exemple, distingir entre un URIError o altres errors de programació), tractar-ho si saps com actuar, i, si no pots gestionar-ho, tornar a llançar l'error amb throw. En fer això, el control saltarà al següent manejador .catch() de la cadena, permetent dissenyar estratègies de recuperació en capes.

També pots fer que un .catch() “tanqueu” l'error de manera satisfactòria (sense tornar a llançar-lo) i torneu un valor de recuperació. En aquest cas, la cadena continua el curs normal i el següent then s'executa com si tot hagués anat bé, rebent aquest valor alternatiu.

Tingues en compte que .then() podeu rebre dos arguments: el primer per a l'èxit i el segon per a l'error. Aquest segon paràmetre actua com un manejador local d'errors per a aquesta etapa concreta. No obstant això, a la pràctica, és més habitual fer servir .catch() separat, ja que fa el flux derrors més clar.

Què passa amb els errors no manejats?

Si una promesa es rebutja i no n'hi ha cap manejador d'error (.catch() o segon argument de .then()) a la cadena, l'error queda “encallat” i el motor el considera un rebuig no manejat. Això és força similar al que passa amb una excepció síncrona que no està dins d'un bloc try..catch: l'script “rebenta” i veus el missatge a la consola.

Als navegadors moderns pots interceptar aquests errors globalment amb l'esdeveniment unhandledrejection. Aquest esdeveniment rep un PromiseRejectionEvent amb una propietat promise (la promesa que va fallar) i una reason (la causa del rebuig). Amb això pots registrar l'error en un servidor, mostrar un missatge amigable a l'usuari o, com a mínim, evitar que la sentència passi desapercebuda.

També hi ha l'esdeveniment rejectionhandled, que es dispara quan una promesa que havia estat rebutjada acaba rebent un manejador derror més tard. Ambdós esdeveniments permeten muntar una estratègia global d‟observació i tractament d‟errors de promeses.

A Node.js, l'entorn emet esdeveniments similars (unhandledRejection) que per defecte es registren a consola. Pots enganxar-te a aquests esdeveniments per centralitzar el logging d'errors o impedir que s'ompli la sortida estàndard de traces no controlades.

Patrons pràctics per manejar errors amb Promises

Perquè el maneig d'errors amb promeses sigui robust, és important seguir-ne alguns patrons de composició i evitar errors típics que trenquen la cadena de control.

Sempre encadenar i tornar promeses

Un error molt comú és crear una promesa dins d'un .then() però no tornar-la. Si fas això, la cadena es “parteix” en dues: per una banda segueix la promesa original i per altra banda la nova promesa interna, sense que aquesta influeixi en la seqüència principal.

Jotta-cli a Linux
Article relacionat:
Com programar tasques automàtiques amb Crontab a Linux

La regla d'or és “si crees una promesa dins un manejador, torna-la”. Així, el then superior esperarà que aquesta promesa interna acabi (sigui resolta o rebutjada), i els errors seguiran una ruta única cap a un .catch() comú en lloc de escampar-se en manejadors separats.

També convé acabar les cadenes amb un .catch() final. D'aquesta manera t'assegures que qualsevol error no manejat abans acabi sent capturat en algun punt, evitant que acabin com a rebutjos no gestionats a nivell global.

Evitar el nedament innecessari de then i catch

Un altre anti-patró clàssic és niar .then() un dins l'altre en comptes d'encadenar-los. El nidament excessiu condueix a un abast d'errors confús i sol anar de la mà de l'error anterior (no tornar les promeses internes).

El nidament només té sentit quan vols que un manejador catch intern cobreixi únicament una part concreta de la cadena. Per exemple, pots niar un bloc que englobi una sèrie de passos “opcionals” i tenir un catch dedicat que només capturi errors d'aquesta secció, mentre que un catch exterior es reserva per a errors crítics de la resta del flux.

En aquest patró, els errors dins del bloc opcional es gestionen al catch intern i, una vegada atesos, lexecució pot continuar amb tasques crítiques posteriors. Si un error es produeix en una part crítica anterior al bloc opcional, aquesta fallada l'absorbeix únicament el catch final de la cadena.

Embolicar APIs antigues de callbacks en promeses

Moltes APIs antigues, com setTimeout() o algunes llibreries heretades, encara esperen callbacks a l'estil “antic” i no tornen promeses. Si truques directament a aquestes funcions dins de codi basat en promeses, perds la capacitat de capturar errors de forma uniforme.

La solució és embolicar aquestes APIs en un constructor de Promise. Per exemple, podeu crear delay(ms) que torni una promesa resolta després de cert temps usant internament setTimeout. A partir d'aquí, sempre uses la funció embolicada que torna promeses, mai l'API antiga, de manera que tot el teu codi asíncron sigui “promise-friendly”.

A més new Promise(), tens dreceres com Promise.resolve() y Promise.reject() per crear ràpidament promeses ja resoltes o rebutjades, útils per a composició o per a proves.

Compondre promeses en paral·lel o en seqüència

Per executar diverses operacions asíncrones en paral·lel i esperar que totes acabin, disposes de Promise.all(). Aquest mètode rep un array de promeses i torna una promesa que es resol quan totes han tingut èxit, o es rebutja així que una falla.

Si tens un array i vols llançar una promesa per cada element, pots fer servir .map() per transformar-lo en un array de promeses i passar-ho a Promise.all(). Aquest patró és ideal quan no us importa l'ordre de resolució, només que es completin totes.

Per a composició seqüencial amb arrays, pots fer servir reduce() per construir una cadena de promeses equivalent a Promise.resolve().then(func1).then(func2).... Fins i tot és possible encapsular aquest patró en una funció de composició (composeAsync) que rebi diverses funcions (síncrones o asíncrones) i torni una nova funció que encadena totes sobre un valor inicial.

A ECMAScript modern, moltes d'aquestes composicions es tornen més naturals usant async/await, com veurem ara.

Async/Await: sintaxi més neta amb el mateix model d'errors

Les paraules clau async y await aporten una manera més llegible de treballar amb promeses. Una funció marcada com async torna sempre una promesa, encara que a dins sembli que estàs tornant un valor normal. I await et permet pausar l'execució d'aquesta funció fins que una promesa es resolgui o es rebutgi.

Per exemple, una funció async function getUser() pot trucar a await fetch(...) i després a await response.json(), i finalment tornar l'objecte resultant. Per fora, qui invoqui getUser() rep una promesa que es resol amb aquestes dades.

Maneig d'errors amb try/catch en funcions async

El gran benefici de async/await és que pots fer servir Blocs try/catch "De tota la vida" per manejar errors asíncrons. Si una promesa esperada amb await es rebutja, es llança una excepció dins de la funció async, que pots atrapar amb catch.

Això fa que la gestió d'errors s'assembli molt al codi síncron: agrupes els await crítics dins d'un try, analitzes l'excepció al catch, registres detalls tècnics per a depuració, mostres un missatge entenedor a l'usuari i, si escau, tornes a llançar l'error perquè capes superiors decideixin què fer.

També pots tenir un bloc finally que s'executa sempre, tant si hi ha hagut error com si no, ideal per alliberar recursos, resetejar estats de càrrega o tancar connexions temporals.

Execució seqüencial i dependències entre trucades

Quan una operació depèn dels resultats d'una altra, await facilita molt el flux. Pots fer una primera trucada per obtenir informació de l'usuari, extreure'n la URL dels seus repositoris, i després llançar una segona petició amb await, garantint que la segona només s'executa quan la primera s'ha acabat correctament.

Aquest estil evita la proliferació de .then() niats i fa que el codi sembli gairebé síncron, encara que per sota segueixi usant promeses i el mateix model d'execució basat en cues de microtasques.

Top-level await en mòduls

En els mòduls ES moderns es permet fer servir await directament a nivell superior, sense necessitat d'embolicar el codi en una funció async. Això està pensat per a casos com carregar configuració inicial, obtenir dades necessàries per renderitzar o fer una petició fetch el resultat del qual s'usa immediatament per poblar estructures globals del mòdul.

Amb aquest enfocament pots escriure alguna cosa com un const colors = await fetch(...) a la part superior del fitxer, i el mòdul no es considerarà completament avaluat fins que aquesta promesa es resolgui. El model d'errors continua sent el mateix: si aquesta promesa es rebutja i no es maneja, es propagarà com a error del mòdul.

Relació entre promissos i maneig global d'errors

Les promeses resolen un dels problemes fonamentals del model basat en callbacks: capturar tots els errors, incloses les excepcions de programació, en una estructura que facilita la composició funcional de tasques asíncrones.

Quan una promesa es rebutja i ningú no la gestiona, el temps d'execució emet els esdeveniments globals adequats (unhandledrejection en navegador, unhandledRejection a Node.js). Això és molt útil per fer un últim nivell de defensa: registrar l'error, notificar un sistema de monitorització, mostrar un missatge general a l'usuari o, almenys, evitar que aquests errors inundin la consola sense control.

Trucar a event.preventDefault() en aquests manejadors pot desactivar el comportament per defecte de lentorn (per exemple, imprimir la traça a la consola), sempre que hagis decidit gestionar-ho tu mateix d'una altra manera. En qualsevol cas, és recomanable revisar amb cura quines promeses estan sent rebutjades i si es tracta d'errors reals de programació abans d'ignorar-los.

Bones pràctiques per a una estratègia sòlida de maneig derrors asíncrons

Per tancar el cercle, convé resumir algunes pautes que ajuden a tenir un sistema robust de maneig derrors en codi asíncron amb promeses i async/await:

  • col·loca .catch() just on sàpigues com reaccionar. No és obligatori capturar tots els errors; hi ha errors irrecuperables en què el millor és deixar que l'error pugi i només monitoritzar-lo de manera global.
  • Aprofita el segon argument de .then() només quan necessitis tractament molt local. En la majoria de casos, un .catch() únic per cadena és més llegible i menys propens a errors.
  • No saturis a l'usuari amb detalls tècnics. Mostra missatges clars i humans a la interfície, i reserva el detall tècnic (stack traces, payloads, etc.) per al logging intern o els sistemes de monitorització.
  • Centralitza el registre d'errors. Ja sigui amb esdeveniments globals de promeses no manejades, middlewares, interceptors HTTP o utilitats compartides, intenta tenir pocs punts on es decideixi què es registra i com.
  • Torna sempre les promeses que creus i tanca les cadenes amb catch. Això redueix dràsticament els errors no capturats i fa que el teu flux de control sigui predictible.
  • En funcions async, no oblidis await. Una omissió pot provocar que una promesa rebutjada no quedi embolicada pel try/catch, escapant a llocs on no esperaves manejar-la.
manejar errors asíncrons amb try/catch
Article relacionat:
Millors IDEs per programar a Windows

Combinant promeses, try/catch i esdeveniments globals com unhandledrejection, és possible construir aplicacions on els errors asíncrons estan sota control, l'usuari rep missatges clars i l'equip de desenvolupament compta amb la informació necessària per depurar i millorar el codi sense que l'aplicació mori sense explicació per a qui l'està usant. Comparteix aquesta guia de programació i ajuda altres usuaris a resoldre errors asíncrons amb Try/catch.


Afegir com a font preferida a Google