Saltar al contenido principal
09Programación Asíncrona

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 status y, según el caso, value (si se cumplió) o reason (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:

Infografía
Promise.all, race y allSettled con las mismas promesas
5.Resumen: all es todas o ninguna, race la más rápida y allSettled el informe completo.
Paso 5 de 5

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:

AspectoCallbacksPromises
LegibilidadCallback Hell (pirámide)Encadenamiento lineal
Manejo de erroresManual en cada nivelUn solo .catch() centralizado
ComposiciónDifícil de componerFácil con .then() y los métodos estáticos
DepuraciónFlujo difícil de seguirPasos claramente separados
ReutilizaciónLimitadaSe pueden pasar, guardar y combinar
Varias a la vezComplejo de implementarPromise.all() nativo
Control de flujoManual y propenso a erroresMé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:

HerramientaPara 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

  1. Devuelve siempre (return) dentro de los .then() para no romper la cadena.
  2. Termina con un .catch() que recoja los errores que nadie ha manejado.
  3. Deja la limpieza en .finally(): cerrar conexiones, ocultar loaders...
  4. 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.