Promesas en JavaScript ¿Qué son y para qué sirven?

En este artículo, desglosaremos cómo las Promesas transforman el caos de los procesos pendientes en un flujo de código elegante y predecible. Descubre el mecanismo que permite a tus proyectos gestionar esperas y errores sin detener la experiencia del usuario ni un solo segundo.

como usar promesas en javascript

1. ¿Qué es una Promesa?

Una Promesa es un objeto que actúa como un marcador de posición (un placeholder) para un valor que todavía no conocemos, pero que esperamos recibir en el futuro.

Es el mecanismo oficial de JavaScript para gestionar la asincronía de forma ordenada, permitiendo que el programa sepa qué hacer cuando una tarea termine bien o cuando falle.

1.1. ¿Qué es la Asincronía? (En términos simples)

La asincronía es la capacidad de un programa para iniciar una tarea que toma tiempo y, en lugar de quedarse «congelado» esperando el resultado, seguir ejecutando otras instrucciones. Es la técnica que permite que las aplicaciones web sean fluidas y no se bloqueen cada vez que descargan algo de internet.

1.2. Ejemplo escenario: Sin promesas vs. Con promesas

Imagina que estás en una aplicación de clima en tu teléfono:

1.2.1. Escenario A: SIN PROMESAS (Bloqueante)

  1. Presionas el botón «Actualizar clima».
  2. La aplicación le pide los datos al satélite (esto tarda 5 segundos).
  3. El Problema: Durante esos 5 segundos, la pantalla de tu teléfono se queda totalmente congelada. No puedes volver atrás, no puedes ver el mapa, ni siquiera puedes cerrar la app. El programa está «trabado» en la línea de código que pide el clima.
  4. Si la conexión falla, la app simplemente se cierra o se queda en blanco para siempre porque no sabe cómo manejar el error.

1.2.2. Escenario B: CON PROMESAS (Asíncrono)

  1. Presionas el botón «Actualizar clima».
  2. La aplicación lanza la petición y recibe una Promesa al instante.
  3. Mientras la Promesa está en estado Pendiente (Pending), la app te muestra un iconito de carga girando, pero tú puedes seguir usando la app: puedes ver el historial, cambiar de ciudad o entrar a la configuración.
  4. La Solución: Cuando los datos llegan (éxito), la Promesa se resuelve y el texto del clima aparece mágicamente en pantalla. Si no hay internet (fallo), la Promesa se rechaza y la app te muestra un mensaje amigable: «Lo sentimos, revisa tu conexión».

2. ¿Para que sirven las promesas?

Estas son sus principales funcionalidades:

2.1. Para «No congelar» tu aplicación

Imagina que JavaScript es un cocinero que solo tiene una mano. Si ese cocinero se pone a hervir agua y se queda mirando la olla sin soltarla hasta que hierva, no puede cortar las verduras ni atender a los clientes. La cocina se detiene.

  • SIN PROMESAS: El navegador se queda bloqueado esperando una respuesta (como una descarga). El usuario intenta hacer clic en un botón y no pasa nada.
  • CON PROMESAS: El cocinero pone la olla al fuego, deja un «aviso» (la Promesa) que sonará cuando hierva, y sigue cortando verduras. Las promesas sirven para que tu página web siga siendo fluida y respondona mientras ocurren cosas pesadas de fondo.

2.2. Para gestionar la incertidumbre

Cuando pides algo por internet (como los datos de un usuario), no sabes con certeza qué va a pasar. Hay tres futuros posibles que la Promesa gestiona por ti:

  1. El futuro brillante:
    • Los datos llegan rápido y perfectos. (Estado: Fulfilled/Resuelta).
  2. El futuro gris:
    • No hay internet, el servidor se cayó o el usuario no existe. (Estado: Rejected/Rechazada).
  3. El presente:
    • Todavía no sabemos qué va a pasar. (Estado: Pending/Pendiente).

Las promesas sirven para que tú, como programador, dejes instrucciones escritas para cada caso: «Si sale bien, haz A; si sale mal, haz B». Sin ellas, tu código simplemente fallaría o intentaría usar datos que no existen.

2.3. Para ordenar el caos (El «Paso a paso»)

A veces necesitas que las cosas pasen en un orden específico, pero son tareas asíncronas. Por ejemplo:

  1. Iniciar sesión.
  2. Luego, obtener el perfil del usuario.
  3. Luego, mostrar sus fotos.

Sin promesas, esto se convertía en un código desordenado y difícil de leer (el famoso Callback Hell). Las promesas sirven para encadenar estas tareas una detrás de otra de forma legible, como si fuera una lista de compras: «Haz esto, luego haz esto otro, luego aquello».

En resumen, las promesas sirven para:

  1. Esperar sin detenerse:
    • Mantener la web activa mientras se cargan datos.
  2. Prepararse para el error:
    • Tener un plan de emergencia si algo falla en la red.
  3. Leer el código como un libro:
    • Poder seguir la lógica de arriba hacia abajo, aunque las tareas tarden diferentes tiempos en completarse.

3. ¿Cómo usar promesas?

Aquí veremos las dos situaciones más comunes usando promesas

  • Usando en constructor new Promise()
  • Usando funciones que retornan promesas

3.1. Sintaxis general usando new Promise()

Para crear una promesa, usamos el constructor new Promise(). Este siempre recibe una función (llamada executor) que se ejecuta inmediatamente.

const promesa = new Promise((resolve, reject) => {
  // Lógica asincrónica
});Lenguaje del código: JavaScript (javascript)

3.1.1. Ejemplo completo

/**
 * --- EL CONSTRUCTOR: 'new Promise' ---
 * Es la forma de decirle a JS: "Voy a iniciar una tarea que terminará luego".
 */
const promesa = new Promise((resolve, reject) => {

  // 👉 new Promise(...)
  // Crea un objeto Promesa que manejará una operación asincrónica

  // 👉 (resolve, reject) => { ... }
  // Es una función ejecutora que se ejecuta automáticamente
  // Tiene dos parámetros importantes:

  // Simulamos una operación asincrónica (ej: API)
  setTimeout(() => {

    const exito = true;

    if (exito) {
      resolve("Operación exitosa"); 
      // 👉 resolve(valor)
      // Cambia el estado a "fulfilled"
      // Envía el resultado a los .then()
    } else {
      reject("Ocurrió un error"); 
      // 👉 reject(error)
      // Cambia el estado a "rejected"
      // Envía el error a los .catch()
    }

  }, 2000);

});

/**
 * --- CONSUMO DE LA PROMESA ---
 *  Una vez que la promesa está creada, necesitamos "escuchar" cuándo termina. Para eso usamos tres métodos principales:
 * .then() -> El "Camino del Éxito" - .catch() -> El "Manejador de Emergencias" - .finally() -> El "Paso Final"
 */
  .then((resultado) => {
    // 👉 .then()
    // Se ejecuta cuando la promesa se resuelve (resolve)
    // Recibe el valor pasado en resolve()    

   console.log(resultado);
  })
  .catch((error) => {
    // 👉 .catch()
    // Se ejecuta cuando la promesa falla (reject)
    // Recibe el error pasado en reject()    

   console.error(error);
  })
  .finally(() => {
    // 👉 .finally()
    // Se ejecuta siempre (éxito o error)
    // Ideal para limpiar estados (ej: loaders en el DOM)    

   console.log("Proceso finalizado");
  });Lenguaje del código: JavaScript (javascript)

Componentes clave de la sintaxis:

  1. new Promise()
    • Constructor que crea la promesa
    • Recibe una función ejecutora
  2. Función ejecutora (resolve, reject) => {}
    • Se ejecuta automáticamente
    • Contiene la lógica asincrónica
  3. resolve(valor)
    • Indica éxito
    • Cambia el estado a fulfilled
    • Envía datos a .then()
  4. reject(error)
    • Indica fallo
    • Cambia el estado a rejected
    • Envía error a .catch()
  5. .then()
    • Maneja el resultado exitoso
    • Se puede encadenar
  6. .catch()
    • Maneja errores
    • Captura fallos de toda la cadena
  7. .finally()
    • Se ejecuta siempre
    • Ideal para limpiar (ej: ocultar loader en DOM)

3.2. Sintaxis usando funciones que retornan promesas

Esta es, de hecho, la forma más común y profesional de trabajar. En lugar de tener una promesa suelta, la envolvemos en una función. Esto nos permite reutilizarla y pasarle parámetros (como un ID de usuario o una URL).

Aquí tienes la sintaxis detallada:

function nombreFuncion() {
  return new Promise((resolve, reject) => {
    // lógica asincrónica
  });
}Lenguaje del código: JavaScript (javascript)

3.2.2. Ejemplo completo

En esta parte, creamos una función normal que, al ser ejecutada, nos entrega (retorna) el objeto Promesa.

/**
 * DEFINICIÓN DE LA FUNCIÓN
 * @param {string} usuario - Recibe un nombre para personalizar la tarea.
 * Retorna una New Promise para que quien la llame pueda usar .then() y .catch()
 */
const obtenerPermisos = (usuario) => {
  // Retornamos la promesa directamente
  return new Promise((resolve, reject) => {
    
    console.log(`Verificando permisos para: ${usuario}...`);

    // Simulamos una operación asíncrona de 1.5 segundos
    setTimeout(() => {
      const esAdministrador = true; 

      if (esAdministrador) {
        // Si tiene éxito, mandamos un objeto con la info
        resolve({ acceso: true, nivel: "VIP" });
      } else {
        // Si falla, mandamos el motivo del rechazo
        reject("Acceso denegado: No tienes rango suficiente.");
      }
    }, 1500);
  });
};
Lenguaje del código: JavaScript (javascript)

EJECUCIÓN: ¿Cómo se usa esta función?

Al invocar la función, esta «explota» y se convierte en una promesa. Por eso podemos encadenar los métodos de escucha inmediatamente después de los paréntesis.

/**
 * LLAMADA A LA FUNCIÓN
 * Al ejecutar obtenerPermisos("Alex"), 
 * lo que recibimos es la promesa en estado 'pending'.
 */
obtenerPermisos("Alex")
  .then((resultado) => {
    // 'resultado' es el objeto { acceso: true, nivel: "VIP" }
    console.log(`¡Bienvenido! Tu nivel es: ${resultado.nivel}`);
  })
  .catch((error) => {
    // Si la validación fallara, entraría aquí
    console.error("Error de sistema:", error);
  })
  .finally(() => {
    console.log("Proceso de login terminado.");
  });Lenguaje del código: JavaScript (javascript)

Explicación de los Componentes en este Modo:

  1. return new Promise(...):
    • Es vital. Si no pones el return, cuando llames a la función recibirás undefined y no podrás usar el .then().
  2. Parámetros de la función (usuario):
    • A diferencia de una promesa estática, aquí puedes inyectar datos dinámicos para que la promesa trabaje con ellos.
  3. Encadenamiento directo:
    • Como la función devuelve una promesa, puedes escribir miFuncion().then(). Esto hace que el código sea muy limpio.

¿Por qué es mejor usar funciones?

  • Reutilización: Puedes llamar a obtenerPermisos() diez veces con diez nombres distintos.
  • Modularidad: Puedes tener tus promesas en un archivo aparte y exportarlas como herramientas listas para usar.
  • Preparación para Async/Await: Esta estructura es obligatoria si luego quieres usar la sintaxis moderna de async/await.

4. El método .then()

4.1. ¿Qué es exactamente .then()?

Es un método que tienen todos los objetos Promise. Su función es registrar un «callback» (una función) que se ejecutará únicamente cuando la promesa pase al estado de Fulfilled (resuelta).

4.2. Sintaxis y Componentes

Cuando consumes una promesa, .then() recibe como argumento el valor que fue enviado desde el resolve() original.

/**
 * 1. SUPONEMOS UNA FUNCIÓN QUE DEVUELVE UNA PROMESA
 * (Como una petición a una base de datos)
 */
const obtenerUsuario = (id) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // Al resolver, enviamos un objeto
      resolve({ id: id, nombre: "Marcos", rol: "Editor" });
    }, 1000);
  });
};

/**
 * 2. CONSUMO CON .then()
 * Llamamos a la función y "encadenamos" el método.
 */
obtenerUsuario(10)
  .then((usuario) => {
    // COMPONENTE: El parámetro 'usuario'
    // Es el objeto que 'resolve' nos mandó desde la promesa.
    
    console.log("¡Promesa cumplida!");
    console.log(`Bienvenido, ${usuario.nombre}`); // Imprime: "Bienvenido, Marcos"
  });
Lenguaje del código: JavaScript (javascript)

4.3. El Encadenamiento (La verdadera potencia)

Una de las características más importantes de .then() es que siempre devuelve una nueva promesa. Esto nos permite encadenar múltiples acciones una tras otra sin caer en el desorden.

Si dentro de un .then() retornas un valor, ese valor se pasa automáticamente al siguiente .then().

obtenerUsuario(10)
  .then((usuario) => {
    console.log("Paso 1: Usuario obtenido.");
    // Retornamos solo el nombre para el siguiente paso
    return usuario.nombre.toUpperCase(); 
  })
  .then((nombreMayus) => {
    // 'nombreMayus' es el resultado del return anterior
    console.log(`Paso 2: Nombre transformado: ${nombreMayus}`);
    return nombreMayus.length;
  })
  .then((longitud) => {
    // Seguimos la cadena...
    console.log(`Paso 3: El nombre tiene ${longitud} letras.`);
  })
  .catch((error) => {
    // Un solo catch al final atrapa errores de CUALQUIER .then anterior
    console.error("Algo falló en la cadena:", error);
  });
Lenguaje del código: JavaScript (javascript)

4.4. Puntos clave para recordar

  1. No es inmediato:
    • Aunque la promesa se resuelva instantáneamente, el código dentro del .then() se ejecutará en el siguiente «ciclo» de JavaScript (no bloquea el resto del script).
  2. Recibe el éxito:
    • Si la promesa falla (reject), el .then() se salta por completo y el flujo va directo al .catch().
  3. Transformación:
    • Puedes usar .then() para procesar datos (limpiar un texto, filtrar una lista) antes de que lleguen a la parte final de tu programa.

5. El método .catch()

5.1. ¿Qué es .catch()?

El método .catch() es el escudo protector de tu código asíncrono. Si .then() es para cuando todo sale bien, .catch() es el plan de emergencia para cuando algo sale mal.

Es un método que se encadena a una Promesa para capturar cualquier error o rechazo que ocurra durante la operación asíncrona. Su trabajo es detener la «caída libre» de un error y permitirte manejarlo con elegancia, evitando que tu aplicación deje de funcionar o muestre mensajes extraños al usuario.

5.2. ¿Para qué sirve? (Funciones principales)

  1. Capturar el reject():
    • Si dentro de la promesa llamaste a la función reject("Error de red"), el bloque .catch() recibirá ese mensaje.
  2. Capturar errores inesperados:
    • Si dentro de un .then() cometes un error de programación (como intentar leer una propiedad de algo que es undefined), el .catch() también lo atrapará.
  3. Centralizar el manejo de fallos:
    • No necesitas un detector de errores en cada paso; un solo .catch() al final de una cadena de varios .then() puede vigilar a todos.

5.3. Sintaxis y ejemplo

Observa cómo el flujo de ejecución «salta» directamente al .catch() cuando algo falla:

/**
 * 1. DEFINICIÓN: Una función que puede fallar.
 * Simulamos la validación de un pago.
 */
const procesarPago = (monto) => {
  return new Promise((resolve, reject) => {
    console.log("Procesando pago...");

    setTimeout(() => {
      // Condición de error: monto insuficiente
      if (monto > 0) {
        resolve(`Pago de $${monto} aprobado.`);
      } else {
        // ACTIVACIÓN DEL ERROR
        reject("Error: El monto debe ser mayor a cero.");
      }
    }, 2000);
  });
};

/**
 * 2. CONSUMO Y MANEJO DE ERRORES
 */
procesarPago(-50) // Enviamos un número negativo para forzar el error
  .then((confirmacion) => {
    // Este bloque SE SALTARÁ porque la promesa será rechazada
    console.log(confirmacion);
  })
  .catch((error) => {
    /**
     * COMPONENTE: El parámetro 'error'
     * Recibe lo que sea que mandaste en el reject().
     */
    console.error("Hubo un problema con tu transacción:");
    console.error(`Motivo: ${error}`); // Imprime: Motivo: Error: El monto debe ser mayor a cero.
  })
  .finally(() => {
    console.log("Sesión de pago cerrada.");
  });
Lenguaje del código: JavaScript (javascript)

5.4. Características Importantes

  1. El Efecto «Burbuja»:
    • En una cadena de promesas, el error «flota» hacia abajo. Si tienes 5 .then() seguidos y el error ocurre en el primero, JavaScript saltará los otros 4 y caerá directo en el .catch().
  2. Recuperación:
    • Sorprendentemente, puedes poner un .then() después de un .catch(). Esto sirve si quieres intentar una «solución alternativa» después de un fallo.
  3. Evita el «Uncaught Promise Rejection»:
    • Si una promesa falla y no tienes un .catch(), el navegador lanzará una advertencia roja en la consola que puede afectar el rendimiento o la estabilidad de tu app.

6. El método .finally()

6.1. ¿Qué es .finally()?

El método .finally() es el último eslabón en la cadena de una Promesa, se ejecuta siempre, sin importar si la Promesa fue:

  • ❌ rechazada (reject)
  • ✅ resuelta (resolve)

Si el .then() es para el éxito y el .catch() es para el error, el .finally() es para lo que tiene que pasar sí o sí, sin importar el resultado.

A diferencia de los otros dos, .finally() no recibe ningún argumento. No sabe si la promesa terminó bien o mal, simplemente sabe que terminó.

6.2. Sintaxis y ejemplo.

SINTAXIS:

miPromesa()
  .then(resultado => {
    // Se ejecuta si todo sale bien
  })
  .catch(error => {
    // Se ejecuta si ocurre un error
  })
  .finally(() => {
    // ✅ Se ejecuta SIEMPRE (éxito o error)
  });Lenguaje del código: JavaScript (javascript)

EJEMPLO:

Observa cómo este bloque es el «punto de encuentro» final del código:

/**
 * Simulamos una petición de descarga de un archivo
 */
const descargarArchivo = () => {
  return new Promise((resolve, reject) => {
    console.log("Iniciando descarga...");
    const hayInternet = false; // Simulamos un fallo

    setTimeout(() => {
      if (hayInternet) {
        resolve("Archivo.pdf descargado");
      } else {
        reject("Error 500: Sin conexión");
      }
    }, 2000);
  });
};

// --- CONSUMO ---

// 1. Activamos un indicador de carga en la UI (imaginario)
console.log("Estado: [CARGANDO...]"); 

descargarArchivo()
  .then((mensaje) => {
    // Solo se ejecuta si hay éxito
    console.log("Éxito:", mensaje);
  })
  .catch((error) => {
    // Solo se ejecuta si hay error
    console.error("Fallo:", error);
  })
  .finally(() => {
    /**
     * COMPONENTE: .finally()
     * No importa si se descargó o si falló la red, 
     * el estado de "Cargando" debe desaparecer siempre.
     */
    console.log("Estado: [LISTO / ESPERA]");
    console.log("Limpieza de memoria realizada.");
  });Lenguaje del código: JavaScript (javascript)

6.3. ¿Para qué sirve? (Su función principal)

Sirve para realizar tareas de limpieza o finalización que son necesarias en ambos casos (éxito o fracaso).

Casos de uso comunes:

  1. Quitar un «Loading»:
    • Si muestras un icono de carga (spinner) al usuario mientras pides datos, debes quitarlo tanto si los datos llegaron como si hubo un error de conexión.
  2. Cerrar conexiones:
    • Cerrar una conexión a una base de datos o un archivo que dejaste abierto.
  3. Resetear formularios:
    • Limpiar los campos de un formulario después de intentar enviar los datos.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *