Promises (Promesas) - Avanzado
Clase 3 de 511 min de lectura
Ya sabes crear una promesa, consumirla y encadenarla. Pero ¿qué haces cuando tienes tres a la vez? ¿Y si una se cuelga y no responde nunca? Aquí están las herramientas para coordinar varias promesas y controlar sus errores con precisión.
Promise.resolve() y Promise.reject()#
Son dos atajos para crear promesas que ya nacen cumplidas o rechazadas, sin escribir new Promise(...). Vienen muy bien para convertir un valor normal en una promesa y para hacer pruebas.
Mira los dos primeros casos y, sobre todo, el tercero, que es donde se ve su utilidad:
// Promise que se resuelve inmediatamenteconst promesaExitosa = Promise.resolve('¡Éxito inmediato!')promesaExitosa.then(resultado => { console.log(resultado) // -> "¡Éxito inmediato!"})// Promise que se rechaza inmediatamenteconst promesaFallida = Promise.reject('Error inmediato')promesaFallida.catch(error => { console.log('Error:', error) // -> "Error: Error inmediato"})// Útil para normalizar valoresfunction procesarDatos(datos) { // Si ya tienes los datos, los conviertes en Promise if (datos) { return Promise.resolve(datos) } // Si no, haces una llamada asíncrona return fetch('/api/datos').then(response => response.json())}procesarDatos a veces ya tiene los datos y a veces tiene que ir a buscarlos. Gracias a Promise.resolve(), devuelve siempre una promesa, y quien la use puede hacer .then() sin preguntarse en qué caso está.
Métodos Útiles de Promise#
Cuando hay varias promesas en juego, la pregunta es: ¿necesito todas, la primera, o saber cómo acabó cada una? Hay un método estático para cada respuesta.
Promise.all(): Todas o ninguna
Úsalo cuando necesitas que todas las operaciones terminen bien. Es como una cena en la que no se empieza hasta que llega el último invitado.
- Espera a todas las promesas y devuelve un array con los resultados, en el mismo orden en que las pasaste.
- Si una sola falla,
Promise.all()se rechaza con ese error. - No las espera de una en una: todas están en marcha a la vez.
Fíjate en los tiempos de cada descarga y en el orden del array final:
function descargarArchivo(nombre, tiempo) { return new Promise((resolve) => { setTimeout(() => { console.log(`📁 ${nombre} descargado`) resolve(`Contenido de ${nombre}`) }, tiempo) })}const promesas = [ descargarArchivo('imagen1.jpg', 1000), descargarArchivo('imagen2.jpg', 1500), descargarArchivo('imagen3.jpg', 800)]Promise.all(promesas) .then((resultados) => { console.log('🎉 Todas las descargas completadas:') console.log(resultados) // ['Contenido de imagen1.jpg', 'Contenido de imagen2.jpg', 'Contenido de imagen3.jpg'] }) .catch((error) => { console.log('❌ Alguna descarga falló:', error) })imagen3.jpg termina la primera, pero en el array sigue siendo la tercera. Y todo tarda 1,5 segundos (lo que tarda la más lenta), no la suma de las tres.
Promise.race(): La Primera que Termine
Úsalo cuando solo te interesa la más rápida. Es una carrera: gana la primera que cruza la meta y el resto ya no importa.
- Se queda con la primera promesa que termine, tanto si se cumple como si se rechaza.
- Ideal para poner límites de tiempo (timeouts) y para pedir lo mismo a varios servidores.
- Las demás no se cancelan: siguen ejecutándose, solo que su resultado se ignora.
function servidor(nombre, tiempo) { return new Promise((resolve) => { setTimeout(() => { resolve(`Respuesta del ${nombre}`) }, tiempo) })}const servidores = [ servidor('Servidor A', 2000), servidor('Servidor B', 1000), // Este será el más rápido servidor('Servidor C', 3000)]Promise.race(servidores) .then((respuesta) => { console.log('🏆 Primer servidor en responder:', respuesta) // "Respuesta del Servidor B" (después de 1 segundo) })// Ejemplo práctico: Timeoutfunction operacionConTimeout(promesa, tiempoLimite) { const timeout = new Promise((_, reject) => { setTimeout(() => { reject('⏰ Operación expirada') }, tiempoLimite) }) return Promise.race([promesa, timeout])}La segunda parte es el uso más práctico. operacionConTimeout pone a competir tu promesa contra otra que se rechaza al acabarse el tiempo. Si la tuya tarda demasiado, gana el timeout y recibes el error en lugar de quedarte esperando sin fin.
Promise.allSettled(): Todas, sin importar el resultado
Úsalo cuando quieres saber cómo ha acabado cada una, aunque algunas fallen. Es un parte de estado: qué funcionó y qué no.
- Espera a que todas terminen, bien o mal.
- Nunca se rechaza: siempre te entrega todos los resultados.
- Devuelve un array de objetos con
statusy, según el caso,value(si se cumplió) oreason(si se rechazó).
function operacion(nombre, exito, tiempo) { return new Promise((resolve, reject) => { setTimeout(() => { if (exito) { resolve(`${nombre} exitoso`) } else { reject(`${nombre} falló`) } }, tiempo) })}const operaciones = [ operacion('Operación A', true, 1000), operacion('Operación B', false, 800), // Esta falla operacion('Operación C', true, 1200)]Promise.allSettled(operaciones) .then((resultados) => { console.log('📊 Resultados de todas las operaciones:') resultados.forEach((resultado, indice) => { if (resultado.status === 'fulfilled') { console.log(`✅ ${indice + 1}: ${resultado.value}`) } else { console.log(`❌ ${indice + 1}: ${resultado.reason}`) } }) // ✅ 1: Operación A exitoso // ❌ 2: Operación B falló // ✅ 3: Operación C exitoso })Con Promise.all(), el fallo de la operación B lo habría tirado todo abajo. Aquí revisas el status de cada resultado y decides qué hacer con los que fallaron.
Comparando all, race y allSettled
Para ver la diferencia de un vistazo, lanzamos las mismas tres promesas con cada método:
all es todas o ninguna, race la más rápida y allSettled el informe completo.Manejo de Errores Avanzado#
Con promesas no solo capturas errores: decides dónde capturarlos y qué pasa después. Dos piezas te dan ese control.
.finally(): Siempre se ejecuta
Es el sitio para el código de limpieza, el que debe ejecutarse pase lo que pase.
- Se ejecuta siempre, haya éxito o error.
- Perfecto para limpiar: cerrar conexiones, ocultar el indicador de carga...
- No recibe argumentos ni cambia el valor de la promesa, así que la cadena puede continuar después.
El caso típico es un loader: lo muestras antes de empezar y quieres ocultarlo al terminar, vaya bien o mal.
function operacionImportante(exito) { return new Promise((resolve, reject) => { console.log('🔄 Iniciando operación...') setTimeout(() => { if (exito) { resolve('Operación completada') } else { reject('Operación falló') } }, 2000) })}// Ejemplo práctico: Mostrar/ocultar loaderfunction mostrarLoader() { console.log('⏳ Mostrando loader...')}function ocultarLoader() { console.log('✅ Ocultando loader...')}mostrarLoader()operacionImportante(true) // Cambia a false para probar el error .then((resultado) => { console.log('✅ Éxito:', resultado) }) .catch((error) => { console.log('❌ Error:', error) }) .finally(() => { ocultarLoader() // Siempre se ejecuta console.log('🏁 Operación terminada') })Sin .finally() tendrías que llamar a ocultarLoader() dos veces: en el .then() y en el .catch().
Múltiples .catch(): Manejo granular de errores
Un .catch() no tiene por qué ir al final. Puedes colocar varios a lo largo de la cadena para tratar cada error donde te interese. Sigue el recorrido de este ejemplo, donde paso1 falla nada más empezar:
function paso1() { return Promise.reject('Error específico del paso 1')}function paso2() { return Promise.resolve('Paso 2 OK')}paso1() .catch((error) => { console.log('🔧 Recuperándose del error:', error) return 'Valor de recuperación' // Continuamos la cadena }) .then((resultado) => { console.log('➡️ Continuando con:', resultado) return paso2() }) .then((resultado) => { console.log('🎉 Final exitoso:', resultado) }) .catch((error) => { console.log('💥 Error no recuperable:', error) })El primer .catch() atrapa el error y devuelve un valor. Con eso la cadena se da por recuperada y sigue por los .then() como si nada. El último .catch() queda de red de seguridad, por si falla algo de ahí en adelante.
Comparación: Callbacks vs Promises#
Con todo lo que has visto en estas dos clases, ya puedes poner una cosa al lado de la otra:
| Aspecto | Callbacks | Promises |
|---|---|---|
| Legibilidad | Callback Hell (pirámide) | Encadenamiento lineal |
| Manejo de errores | Manual en cada nivel | Un solo .catch() centralizado |
| Composición | Difícil de componer | Fácil con .then() y los métodos estáticos |
| Depuración | Flujo difícil de seguir | Pasos claramente separados |
| Reutilización | Limitada | Se pueden pasar, guardar y combinar |
| Varias a la vez | Complejo de implementar | Promise.all() nativo |
| Control de flujo | Manual y propenso a errores | Métodos especializados |
¿Por qué son mejores las Promises?
Porque el flujo es lineal, los errores se manejan en un único lugar y tienes herramientas que con callbacks tendrías que programar a mano.
🛠️ Herramientas que ahora dominas#
Esta es tu caja de herramientas completa para trabajar con promesas:
| Herramienta | Para qué sirve |
|---|---|
.then() | Recoger el valor y transformarlo |
.catch() | Capturar errores en cualquier punto |
.finally() | Limpieza que se ejecuta siempre |
Promise.all() | Necesitas todas, y todas deben cumplirse |
Promise.race() | Te quedas con la más rápida |
Promise.allSettled() | Informe completo de cómo acabó cada una |
Promise.resolve() | Crear una promesa ya cumplida |
Promise.reject() | Crear una promesa ya rechazada |
Buenas prácticas
- Devuelve siempre (
return) dentro de los.then()para no romper la cadena. - Termina con un
.catch()que recoja los errores que nadie ha manejado. - Deja la limpieza en
.finally(): cerrar conexiones, ocultar loaders... - Coloca un
.catch()intermedio solo cuando puedas recuperarte de ese error concreto.
Próximo nivel: fetch y async/await
En la siguiente clase pondrás todo esto a trabajar con peticiones reales usando fetch. Y después llega async/await: las mismas promesas, pero escritas con una sintaxis que parece código síncrono de toda la vida.
En resumen
Promise.all()espera a todas y falla en cuanto una falla.Promise.race()se queda con la primera que termine, sea éxito o error.Promise.allSettled()nunca se rechaza: te dice cómo acabó cada promesa..finally()se ejecuta siempre, ideal para tareas de limpieza.- Un
.catch()que devuelve un valor recupera la cadena, que sigue por los.then()siguientes.