Promise.race(): Carrera de Promesas en JavaScript
Aprende a usar Promise.race() para obtener el resultado de la primera promesa que se resuelva o rechace, ideal para implementar timeouts y competencias entre múltiples fuentes de datos.
TL;DR - Resumen rápido
- Promise.race() devuelve una promesa que se resuelve o rechaza tan pronto como cualquiera de las promesas del iterable se resuelve o rechaza
- Es ideal para implementar patrones de timeout en operaciones asíncronas
- La primera promesa en completarse (resolverse o rechazarse) determina el resultado final
- Si el iterable está vacío, Promise.race() devuelve una promesa que nunca se resuelve
- A diferencia de Promise.all(), Promise.race() solo necesita UNA promesa para completarse, no todas
Introducción a Promise.race()
Promise.race() es un método estático del objeto Promise que permite ejecutar múltiples promesas en paralelo y obtener el resultado de la primera que se complete. El término "race" (carrera) es literalmente lo que hace: las promesas compiten para ver cuál termina primero, y esa promesa determina el resultado final.
Este método es fundamental cuando necesitas implementar timeouts, cuando tienes múltiples fuentes de datos y quieres usar la respuesta más rápida, o cuando necesitas cancelar una operación asíncrona si otra termina antes. Es una herramienta poderosa para optimizar el rendimiento de aplicaciones que dependen de múltiples operaciones asíncronas.
Diferencia clave con Promise.all()
Mientras que Promise.all() espera a que TODAS las promesas se resuelvan o a que ALGUNA se rechace, Promise.race() solo espera a la PRIMERA promesa que se complete, ya sea resolviéndose o rechazándose. Esto hace que Promise.race() sea ideal para escenarios donde el tiempo es crítico.
Sintaxis Básica
La sintaxis de Promise.race() es simple y directa. Recibe un iterable (generalmente un array) de promesas y devuelve una nueva promesa que se comporta según la primera promesa que se complete.
En este ejemplo, creamos tres promesas con diferentes tiempos de resolución: la primera tarda 1000ms, la segunda 500ms y la tercera 1500ms. Promise.race() devuelve el resultado de la promesa que termina primero, que en este caso es la segunda promesa con valor "Segunda". Es importante notar que aunque la primera y tercera promesa eventualmente se resuelven, sus resultados son ignorados porque ya "perdieron la carrera".
Promesas ya resueltas
Si pasas promesas que ya están resueltas a Promise.race(), la promesa resultante se resolverá inmediatamente con el valor de la primera promesa resuelta en el iterable. Esto es útil cuando quieres combinar promesas que pueden estar en diferentes estados.
Cómo Funciona Promise.race()
Promise.race() sigue un comportamiento determinista basado en el estado de las promesas que recibe. Entender este comportamiento es crucial para usarlo correctamente y evitar bugs sutiles en tu código.
- <strong>Primera en resolverse:</strong> Si la primera promesa en completarse se resuelve, Promise.race() se resuelve con ese valor.
- <strong>Primera en rechazarse:</strong> Si la primera promesa en completarse se rechaza, Promise.race() se rechaza con ese error.
- <strong>Iterable vacío:</strong> Si el iterable está vacío, la promesa nunca se resuelve ni se rechaza.
- <strong>Valores no-promesa:</strong> Los valores que no son promesas se tratan como promesas ya resueltas.
- <strong>Múltiples promesas completadas:</strong> Si varias promesas completan al mismo tiempo, la primera en el iterable determina el resultado.
Patrón Timeout con Promise.race()
Uno de los usos más comunes de Promise.race() es implementar timeouts para operaciones asíncronas. Esto es especialmente útil cuando trabajas con APIs que pueden tardar demasiado o no responder.
Este patrón crea una "carrera" entre la operación asíncrona y una promesa de timeout. Si la operación completa antes del tiempo límite, obtienes su resultado. Si el timeout gana la carrera, obtienes un error que puedes manejar apropiadamente. Es una forma elegante de implementar timeouts sin necesidad de funciones clearTimeout complejas.
Mejor práctica: timeouts personalizados
Encapsula el patrón de timeout en una función reutilizable que acepte una promesa y un tiempo límite. Esto te permite aplicar timeouts consistentemente a todas tus operaciones asíncronas sin repetir código.
Casos de Uso Prácticos
Promise.race() tiene aplicaciones prácticas más allá de los timeouts. Veamos algunos escenarios donde este método brilla realmente en aplicaciones del mundo real.
Fuentes de Datos Alternativas
Cuando tienes múltiples fuentes para obtener el mismo dato (por ejemplo, diferentes servidores o caches), puedes usar Promise.race() para obtener la respuesta más rápida.
En este ejemplo, intentamos obtener datos de tres fuentes diferentes: el servidor principal, un servidor de backup y el cache local. La primera fuente en responder gana, lo que puede reducir significativamente el tiempo de espera para el usuario. Es un patrón común en aplicaciones que requieren alta disponibilidad y baja latencia.
Manejo de Errores
Es crucial entender cómo Promise.race() maneja los errores. Si la primera promesa en completarse se rechaza, la promesa resultante también se rechaza, independientemente de si otras promesas se resuelven correctamente después.
Aquí, la segunda promesa se rechaza inmediatamente (0ms), por lo que Promise.race() se rechaza con ese error. Aunque la primera promesa se resolvería después de 1000ms, su resultado nunca se usa porque la promesa ya se rechazó. Esto demuestra que Promise.race() es "rápido para fallar" - cualquier error que ocurra primero causará que toda la operación falle.
Errores Comunes
Al trabajar con Promise.race(), hay varios errores que los desarrolladores cometen frecuentemente. Conocer estos errores te ayudará a evitarlos y escribir código más robusto.
- <strong>Ignorar promesas pendientes:</strong> Las promesas que pierden la carrera siguen ejecutándose en segundo plano, lo que puede causar fugas de memoria o efectos secundarios inesperados.
- <strong>No manejar rechazos:</strong> Si la primera promesa se rechaza y no tienes un catch(), obtendrás un error no manejado que puede romper tu aplicación.
- <strong>Iterable vacío:</strong> Pasar un array vacío a Promise.race() crea una promesa que nunca se resuelve, lo que puede causar que tu código se quede esperando indefinidamente.
- <strong>Confundir con Promise.any():</strong> Promise.any() espera la primera promesa que se RESUELVA (ignorando rechazos), mientras que Promise.race() espera la primera que se COMPLETE (resuelva O rechace).
- <strong>No cancelar operaciones perdidas:</strong> Las promesas que pierden la carrera pueden seguir consumiendo recursos (como peticiones HTTP) si no las cancelas explícitamente.
Advertencia: Promesas no cancelables
Las promesas nativas de JavaScript NO se pueden cancelar. Una vez que creas una promesa, se ejecutará hasta completarse. Si usas Promise.race() con peticiones HTTP, las peticiones que pierden la carrera seguirán ejecutándose. Para cancelarlas, necesitas usar AbortController con fetch() o implementar tu propio mecanismo de cancelación.
Resumen: Promise.race()
Conceptos principales:
- •Promise.race() devuelve una promesa que se resuelve o rechaza cuando la primera promesa del iterable se completa
- •Es ideal para implementar timeouts y competencias entre múltiples fuentes de datos
- •La primera promesa en completarse (resolverse o rechazarse) determina el resultado final
- •Si el iterable está vacío, la promesa nunca se resuelve ni se rechaza
- •A diferencia de Promise.any(), Promise.race() se rechaza si la primera promesa se rechaza
- •Las promesas que pierden la carrera siguen ejecutándose en segundo plano
Mejores prácticas:
- •Usa Promise.race() para implementar timeouts en operaciones asíncronas críticas
- •Combínalo con AbortController para cancelar peticiones HTTP que pierden la carrera
- •Siempre maneja los rechazos con catch() para evitar errores no manejados
- •Usa Promise.race() cuando tengas múltiples fuentes y quieras la respuesta más rápida
- •Considera Promise.any() si quieres ignorar rechazos y esperar la primera resolución
- •Encapsula patrones comunes (como timeout) en funciones reutilizables