Manejo de Errores en Async/Await
Aprende técnicas específicas para manejar errores en código asíncrono con async/await, incluyendo try-catch, promesas y patrones avanzados de recuperación.
TL;DR - Resumen rápido
- Usa try/catch para capturar errores en funciones async/await
- Siempre usa await o .catch() para evitar unhandled promise rejections
- Los errores en async/await se comportan como errores síncronos
- Puedes anidar try/catch para manejar errores en diferentes niveles
- Usa patrones como reintentos y fallback para mejorar la resiliencia
Introducción
El manejo de errores en código asíncrono es fundamental para aplicaciones modernas que dependen de operaciones como llamadas a APIs, bases de datos y eventos del usuario. Aunque async/await hace el código asíncrono más legible, los errores deben manejarse explícitamente para evitar que las aplicaciones colapsen.
A diferencia del código síncrono, donde los errores se propagan automáticamente hasta encontrar un catch, en código asíncrono los errores pueden quedar sin capturar si no se manejan correctamente. Esto puede resultar enunhandled promise rejections, que pueden causar comportamientos inesperados en tu aplicación.
Unhandled Promise Rejections
Cuando una promesa es rechazada y no hay un .catch() otry/catch que la capture, se produce un unhandled promise rejection. En navegadores modernos, esto puede causar que la aplicación se comporte de forma impredecible o muestre advertencias en la consola.
Try-Catch con Async/Await
La forma más común de manejar errores en async/await es usando bloques try/catch. Este patrón hace que el código asíncrono se comporte como código síncrono, con errores capturados de forma predecible.
Sintaxis Básica
La sintaxis básica de try/catch con async/awaites idéntica a la versión síncrona. El bloque try contiene el código asíncrono que puede fallar, y el bloque catch captura cualquier error que ocurra.
Este ejemplo muestra la sintaxis básica de try/catch conasync/await. Cuando ocurre un error en el bloque try, la ejecución salta inmediatamente al bloque catch, donde puedes manejar el error de forma controlada.
- <strong>try</strong>: Contiene el código asíncrono que puede fallar
- <strong>await</strong>: Pausa la ejecución hasta que la promesa se resuelva
- <strong>catch</strong>: Captura cualquier error que ocurra en el bloque try
- <strong>finally</strong>: Se ejecuta siempre, haya error o no (opcional)
Múltiples Await en un Try
Puedes tener múltiples operaciones await en un solo bloquetry. Si alguna de estas operaciones falla, el error se captura en el bloque catch y las operaciones posteriores no se ejecutan.
Este ejemplo muestra cómo manejar múltiples operaciones await en un solo bloque try/catch. Si la primera operación falla, las operaciones posteriores no se ejecutan. Esto es importante porque las operaciones asíncronas pueden fallar independientemente.
Ejecución Secuencial
Cuando tienes múltiples await en secuencia, cada operación espera a que la anterior termine. Si necesitas ejecutar operaciones en paralelo, usa Promise.all() con try/catch para capturar errores de todas las operaciones.
Captura de Errores en Promesas
Aunque async/await es la forma más moderna de manejar código asíncrono, todavía necesitas entender cómo capturar errores en promesas tradicionales. Esto es especialmente útil cuando trabajas con código legacy o librerías que no usan async/await.
.catch() en Promesas
El método .catch() es la forma tradicional de capturar errores en promesas. Se encadena al final de una cadena de promesas y captura cualquier error que ocurra en la cadena.
Este ejemplo muestra cómo usar .catch() para capturar errores en promesas. El método .catch() captura cualquier error que ocurra en la cadena de promesas, incluyendo errores lanzados con throwo rechazos de promesas. Aunque async/await con try/catches generalmente más legible que encadenar .then() y .catch(), el método .catch() todavía es útil para capturar errores en código que usa promesas tradicionales o cuando prefieres un estilo funcional.
Errores en Promise.all()
Promise.all() ejecuta múltiples promesas en paralelo y se rechaza si alguna de ellas falla. Es importante entender cómo manejar errores cuando usasPromise.all(), ya que el comportamiento es diferente al de las promesas individuales.
Este ejemplo muestra cómo manejar errores en Promise.all(). Cuando alguna de las promesas falla, Promise.all() se rechaza inmediatamente con el error de la primera promesa que falló. Las otras promesas continúan ejecutándose, pero sus resultados se descartan.
- <strong>Promise.all()</strong>: Se rechaza con el primer error que ocurra
- <strong>Promise.allSettled()</strong>: Espera a todas las promesas, devuelve resultados y errores
- <strong>Promise.any()</strong>: Se resuelve con la primera promesa exitosa
- <strong>Promise.race()</strong>: Se resuelve o rechaza con la primera promesa que termine
Cuidado con Promise.all()
Promise.all() se rechaza inmediatamente si alguna promesa falla, incluso si otras promesas todavía están ejecutándose. Si necesitas que todas las promesas terminen independientemente de si fallan o no, usaPromise.allSettled().
Manejo de Errores Anidados
En aplicaciones complejas, a menudo necesitas manejar errores en diferentes niveles de granularidad. Los bloques try/catch pueden anidarse para capturar errores específicos en sub-operaciones mientras mantienes un manejo general en el nivel superior.
Try-Catch Anidado
Los bloques try/catch pueden anidarse para manejar errores en diferentes niveles. Esto es útil cuando una operación tiene múltiples pasos donde cada uno puede fallar de diferentes formas y requiere un manejo específico.
Este ejemplo muestra cómo anidar bloques try/catch para manejar errores específicos en sub-operaciones. El bloque externo maneja errores generales, mientras que los bloques internos manejan errores específicos de cada operación. Si un error no se puede manejar en el nivel interno, se propaga al nivel externo. Si tienes demasiados bloques try/catch anidados, considera extraer sub-operaciones en funciones separadas para mejorar la legibilidad y permitir reutilizar el manejo de errores en diferentes partes de tu aplicación.
Async sin Await: Unhandled Rejections
Uno de los errores más comunes al trabajar con async/await es olvidar usar await en una función async. Esto resulta en un unhandled promise rejection, donde el error no se captura y puede causar problemas en tu aplicación.
El Problema de Async sin Await
Cuando llamas a una función async sin await, la función devuelve una promesa que se ejecuta en segundo plano. Si esta promesa es rechazada y no hay un .catch(), el error queda sin capturar.
Este ejemplo muestra el problema de olvidar await. En el primer caso, la función fetchData() se llama sin await, por lo que el error no se captura. En el segundo caso, el error se captura correctamente con try/catch.
Siempre Usa Await o .catch()
Cuando llames a una función async, siempre usa await o encadena un .catch(). Nunca dejes una promesa sin capturar, ya que esto puede causar unhandled promise rejections y comportamientos inesperados en tu aplicación.
Detectando Unhandled Rejections
Puedes detectar unhandled promise rejections usando el eventounhandledrejection del objeto window. Esto te permite capturar errores que quedaron sin manejar y tomar acciones correctivas.
Este ejemplo muestra cómo detectar unhandled promise rejections usando el evento unhandledrejection. Este evento se dispara cuando una promesa es rechazada y no hay un .catch() que la capture. Puedes usar este evento para registrar errores o mostrar mensajes al usuario. Sin embargo, el evento unhandledrejection es útil solo para detectar errores que quedaron sin manejar, no deberías usarlo como mecanismo principal de manejo de errores. Siempre usa try/catch o .catch() explícitamente para capturar errores de forma intencional.
Patrones Avanzados
Hay varios patrones avanzados para manejar errores en código asíncrono que mejoran la resiliencia de tu aplicación. Estos patrones incluyen reintentos automáticos, valores por defecto y manejo de errores en operaciones en paralelo.
Patrón de Reintentos
El patrón de reintentos intenta una operación fallida varias veces antes de declarar un error. Esto es útil para operaciones que pueden fallar temporalmente, como llamadas a APIs o conexiones a bases de datos.
Este ejemplo muestra el patrón de reintentos con async/await. La función withRetry intenta una operación varias veces, incrementando el tiempo de espera entre intentos. Si todos los intentos fallan, lanza el error final.
Backoff Exponencial
El backoff exponencial incrementa el tiempo de espera entre reintentos exponencialmente (1s, 2s, 4s, 8s...). Esto evita sobrecargar el servidor cuando hay problemas temporales y aumenta las posibilidades de éxito en intentos posteriores.
Patrón de Fallback
El patrón de fallback proporciona valores por defecto o alternativos cuando una operación falla. Esto es especialmente útil para mejorar la experiencia del usuario cuando algunos datos no están disponibles.
Este ejemplo muestra el patrón de fallback con async/await. Cuando una operación falla, el código proporciona un valor por defecto en lugar de propagar el error. Esto permite que la aplicación continúe funcionando incluso cuando algunos datos no están disponibles.
- <strong>Valores por defecto</strong>: Usar catch para retornar valores alternativos
- <strong>Datos cacheados</strong>: Usar datos en cache cuando la API falla
- <strong>UI degradada</strong>: Mostrar una versión simplificada cuando falla un componente
- <strong>Mensaje amigable</strong>: Mostrar un mensaje claro al usuario cuando algo falla
Resumen: Manejo de Errores en Async/Await
Conceptos principales:
- •try/catch captura errores en funciones async/await de forma predecible
- •Los errores en async/await se comportan como errores síncronos
- •Unhandled promise rejections ocurren cuando olvidas await o .catch()
- •Promise.all() se rechaza con el primer error que ocurra
- •Puedes anidar try/catch para manejar errores en diferentes niveles
Mejores prácticas:
- •Siempre usa await o .catch() para evitar unhandled rejections
- •Usa Promise.allSettled() cuando necesitas que todas las promesas terminen
- •Implementa reintentos con backoff para operaciones que pueden fallar temporalmente
- •Usa valores por defecto o fallback para mejorar la experiencia del usuario
- •Detecta unhandled rejections con window.onunhandledrejection para debugging