Manejo de Errores con try/catch en async/await
Aprende técnicas avanzadas para manejar errores en funciones async usando bloques try/catch, finally y patrones de error handling robustos. Domina el manejo de excepciones en código asíncrono.
TL;DR - Resumen rápido
- await convierte promesas rechazadas en errores capturables con try/catch
- Sin try/catch, las promesas rechazadas causan unhandled rejections peligrosas
- finally() se ejecuta siempre, ideal para cerrar conexiones o liberar recursos
- Implementa reintentos con backoff exponencial para operaciones que pueden fallar temporalmente
- Puedes manejar tipos específicos de errores async con múltiples bloques catch
Prerequisito: Fundamentos de try/catch
Este artículo asume que ya conoces los fundamentos de try/catch/finally en código síncrono. Si no estás familiarizado con la sintaxis básica, el objeto Error, o el bloque finally, te recomendamos leer primero try, catch, finally: Manejo de Errores en JavaScript en el módulo de errores.
Introducción al Manejo de Errores con async/await
El manejo de errores en funciones async con try/catch tiene particularidades únicas relacionadas con promesas y operaciones asíncronas. Aunque la sintaxis es similar al código síncrono, los errores provienen de promesas rechazadas, lo que introduce conceptos como unhandled rejections y patrones de reintentos específicos para APIs.
Cuando usas await con una promesa que se rechaza, el error se propaga automáticamente al bloque catch más cercano, similar al código síncrono. Sin embargo, si olvidas el try/catch, obtendrás un "unhandled rejection" en lugar de un error normal, lo que puede causar comportamiento impredecible en producción.
Diferencia clave: Unhandled rejections
A diferencia de errores síncronos que detienen la ejecución inmediatamente, las promesas rechazadas sin try/catch se convierten en "unhandled rejections". En Node.js, esto puede terminar el proceso en versiones recientes. Siempre usa try/catch con await en código que puede fallar.
try/catch con Operaciones await
Cuando usas await, las promesas rechazadas se convierten en errores que puedes capturar con try/catch. Esto hace que el código asíncrono se comporte como código síncrono, pero debes entender que estás capturando rechazos de promesas, no errores síncronos tradicionales.
En este ejemplo, cuando la promesa retornada por `fetchUsuario()` se rechaza, el await detecta el rechazo y lanza un error que es capturado por el catch. Esto es equivalente a usar `.catch()` en promesas, pero con sintaxis más limpia. Sin el try/catch, obtendrías un unhandled rejection.
await convierte rejections en errores
Cuando una promesa se rechaza en await, JavaScript automáticamente lanza un error con el valor de rechazo. Esto permite usar try/catch normal en lugar de .catch(). Es importante entender que sin try/catch, el error se convierte en unhandled rejection, no en un error no capturado tradicional.
Patrones Avanzados de Manejo de Errores
Hay varios patrones avanzados de manejo de errores que puedes usar con async/await para hacer tu código más robusto y mantenible. Estos patrones te permiten manejar errores de manera más específica y proporcionar mejores respuestas a los usuarios.
Múltiples Bloques catch
Puedes usar múltiples bloques catch para manejar diferentes tipos de errores de manera específica. Esto es útil cuando quieres proporcionar diferentes respuestas según el tipo de error que ocurrió.
Aquí, el primer catch() maneja errores de red específicos, mientras que el segundo catch() captura cualquier otro tipo de error. Este patrón te permite proporcionar respuestas más específicas y útiles según el tipo de error que ocurrió, mejorando la experiencia del usuario.
Mejor práctica: errores específicos
Usa múltiples bloques catch cuando necesites manejar diferentes tipos de errores de manera específica. Esto te permite proporcionar respuestas más claras y acciones más apropiadas según el tipo de error que ocurrió, mejorando la experiencia del usuario.
Reintentos con async/await
Puedes usar async/await para implementar lógica de reintentos cuando una operación falla. Esto es útil para operaciones que pueden fallar temporalmente, como peticiones HTTP o conexiones a bases de datos.
En este ejemplo, implementamos una lógica de reintentos que intenta la operación hasta 3 veces antes de rendirse. Si la operación falla, esperamos un tiempo exponencial entre cada intento para evitar sobrecargar el servidor. Este patrón es muy útil para operaciones que pueden fallar temporalmente.
Backoff exponencial
Al implementar reintentos, usa un backoff exponencial (esperar más tiempo entre cada intento). Esto evita sobrecargar el servidor y da tiempo para que el problema se resuelva temporalmente. Un patrón común es esperar 1s, 2s, 4s, etc.
Bloque finally()
El bloque finally() se ejecuta siempre, independientemente de si la operación tuvo éxito o falló. Esto es útil para limpieza de recursos, como cerrar conexiones a bases de datos o liberar memoria, que deben hacerse siempre.
En este ejemplo, el bloque finally() se ejecuta siempre, independientemente de si la operación tuvo éxito o falló. Esto es útil para limpieza de recursos que deben hacerse siempre, como cerrar conexiones o liberar memoria. El finally() es ideal para código de limpieza que no debe fallar.
Mejor práctica: finally para limpieza
Usa finally() para limpieza de recursos que deben hacerse siempre, como cerrar conexiones a bases de datos, liberar memoria, o limpiar archivos temporales. El finally() se ejecuta independientemente del resultado, lo que garantiza que la limpieza siempre ocurra.
Errores Comunes
Al trabajar con try/catch en async/await, hay varios errores que los desarrolladores cometen frecuentemente. Conocer estos errores te ayudará a evitarlos y escribir código más robusto.
- <strong>No usar try/catch:</strong> Olvidar try/catch alrededor de await puede causar errores no capturados.
- <strong>Catch vacío:</strong> Tener un catch() vacío o que no hace nada oculta problemas importantes.
- <strong>Ignorar errores:</strong> No manejar errores de manera significativa hace el debugging difícil.
- <strong>No usar finally:</strong> Olvidar finally() puede causar fugas de recursos.
- <strong>Reintentos infinitos:</strong> Sin límite de reintentos, puedes sobrecargar el servidor.
Advertencia: Errores no capturados
Si una promesa se rechaza en un await y no hay try/catch que la capture, el error se convierte en un "unhandled rejection". Esto puede causar advertencias en la consola y comportamiento impredecible en tu aplicación. Siempre usa try/catch alrededor de operaciones await que pueden fallar.
Resumen: Manejo de Errores con try/catch
Conceptos principales:
- •await convierte promesas rechazadas en errores capturables con try/catch
- •Sin try/catch, obtienes unhandled rejections que pueden terminar el proceso
- •finally() se ejecuta siempre, incluso si hay error o return en try/catch
- •Los errores en await se propagan al bloque catch más cercano automáticamente
- •Puedes usar múltiples catch para manejar diferentes tipos de errores async
- •Los reintentos con backoff exponencial evitan sobrecargar servidores
Mejores prácticas:
- •Siempre usa try/catch con await en operaciones que pueden fallar (fetch, DB, etc.)
- •Implementa backoff exponencial para reintentos (1s, 2s, 4s, 8s...)
- •Usa finally() para cerrar conexiones, archivos y liberar recursos
- •Maneja errores específicos de APIs: NetworkError, TimeoutError, ValidationError
- •Proporciona mensajes claros al usuario sobre qué falló y por qué
- •Registra errores en producción para debugging posterior