Encadenamiento de promesas en JavaScript

El encadenamiento de promesas es la técnica definitiva que transformará tu JavaScript, permitiéndote escribir flujos de datos limpios, secuenciales y fáciles de mantener. En esta guía, desglosaremos paso a paso cómo dominar esta herramienta, desde la teoría fundamental hasta su uso con peticiones reales al servidor

encadenamiento de promesas en javascript

1. ¿Que es un encadenamiento de promesas en JavaScript?

El encadenamiento de promesas (Promise Chaining) es una técnica fundamental en JavaScript que permite ejecutar una serie de operaciones asíncronas de forma secuencial. (Ver que es una promesa en JavaScript)

En lugar de anidar funciones dentro de otras (lo que genera el famoso Callback Hell), el encadenamiento permite que el resultado de una promesa se pase directamente a la siguiente tarea, manteniendo el código limpio y legible.

1.1. ¿Cómo funciona la mecánica?

Cuando usas el método .then(), este siempre devuelve una nueva promesa. Esto es lo que permite «enganchar» otro .then() justo después.

La Promesa Inicial
Es el punto de partida donde se ejecuta una tarea asíncrona, como por ejemplo pedir datos a una API o realizar una operación que lleva tiempo.
El Retorno
Dentro de un .then(), podés devolver un valor simple o una nueva promesa. Este retorno define qué recibirá el siguiente paso de la cadena.
La Sucesión
Cada .then() siguiente espera a que se resuelva lo devuelto en el paso anterior, permitiendo encadenar acciones de forma ordenada y controlada.

2. Sintaxis para crear encadenamientos.

La sintaxis básica del encadenamiento de promesas se construye uniendo bloques lógicos paso a paso. Piensa en ello como una línea de ensamblaje donde cada estación hace su trabajo, transforma el producto y se lo pasa directamente a la siguiente estación.

Aquí tienes la estructura fundamental:

funcionQueDevuelvePromesa()

  // Primer eslabón: Espera a que la promesa inicial se cumpla
  .then((resultado1) => {
    console.log("Paso 1 completado:", resultado1);
    
    // Regla de oro: DEBES retornar algo para alimentar al siguiente eslabón
    return segundaFuncionQueDevuelvePromesa(resultado1); 
  })

  // Segundo eslabón: Espera a que la segunda promesa se cumpla
  .then((resultado2) => {
    console.log("Paso 2 completado:", resultado2);
    
    // También puedes retornar valores normales, no solo promesas
    return "Proceso finalizado con éxito"; 
  })

  // Tercer eslabón: Recibe el valor normal devuelto en el paso anterior
  .then((mensajeFinal) => {
    console.log(mensajeFinal); // Imprime: "Proceso finalizado con éxito"
  })

  // La red de seguridad: Atrapa cualquier error de toda la cadena superior
  .catch((error) => {
    console.error("Hubo un fallo en la línea de ensamblaje:", error);
  });Lenguaje del código: JavaScript (javascript)

Explicación:

  1. El iniciador (funcionQueDevuelvePromesa()): Todo comienza llamando a una operación asíncrona que te entregará una promesa, como solicitar información a una base de datos o leer un archivo.
  2. El método .then(): Es el encargado de responder a la pregunta «¿y qué pasa después?». Este bloque de código solo se ejecuta cuando la promesa que tiene justo arriba se resuelve exitosamente (estado fulfilled). Recibe como parámetro el dato que produjo esa promesa anterior.
  3. La declaración return (El motor de la cadena): Esto es lo que mantiene la cadena viva y conectada. Dependiendo de lo que retornes, el comportamiento cambia:
    • Si retornas una nueva promesa, el siguiente .then() se pausará y esperará pacientemente hasta que esa nueva promesa termine su trabajo.
    • Si retornas un valor normal (como un texto, un número o un objeto), JavaScript lo envuelve automáticamente en una promesa ya resuelta y se lo pasa inmediatamente al siguiente .then().
    • Si no retornas nada (un error muy común cuando estamos aprendiendo), el siguiente .then() se ejecutará de inmediato, pero recibirá undefined.
  4. El método .catch(): Es tu escudo protector centralizado. Si ocurre un error en la promesa inicial, en el primer .then() o en el último, el motor de JavaScript saltará automáticamente todos los .then() restantes y caerá directo en este .catch(). Esto mantiene tu código súper limpio porque no necesitas manejar errores en cada paso individual.

3. Ejemplo de un encadenamiento

Imagina que tienes una aplicación de redes sociales. Para mostrar el perfil de alguien, primero necesitas el ID del usuario (basado en su nombre), y solo después de tener ese ID, puedes pedir sus publicaciones. No puedes hacer ambas cosas a la vez porque la segunda depende de la primera.

VÁMOS CON EL EJEMPLO:

Queremos obtener el nombre del primer post de un usuario específico. El flujo lógico es:

  1. Paso 1: Buscar al usuario por su nombre (Simula una base de datos).
  2. Paso 2: Una vez encontrado, usar su ID para buscar sus posts.
  3. Paso 3: Una vez recibidos los posts, extraer el título del primero.
// Funciones que simulan peticiones a un servidor (asíncronas)
const buscarUsuario = (nombre) => {
  return new Promise((resolve) => {
    setTimeout(() => resolve({ id: 101, nombre: nombre }), 1000);
  });
};

const buscarPosts = (usuarioId) => {
  return new Promise((resolve) => {
    setTimeout(() => resolve([
      { id: 1, titulo: "Mi primer viaje a la playa" },
      { id: 2, titulo: "Aprendiendo JavaScript" }
    ]), 1000);
  });
};

// --- INICIO DEL ENCADENAMIENTO ---
console.log("Iniciando búsqueda...");

buscarUsuario("Alex")
  .then(usuario => {
    console.log(`Usuario ${usuario.nombre} encontrado con ID: ${usuario.id}`);
    // Retornamos una NUEVA promesa para el siguiente eslabón
    return buscarPosts(usuario.id); 
  })

  .then(posts => {
    console.log(`Se han recuperado ${posts.length} posts.`);
    // Retornamos un valor simple (el título)
    return posts[0].titulo;
  })

  .then(tituloFinal => {
    console.log("Resultado final -> Título del post:", tituloFinal);
  })

  .catch(error => {
    console.error("Error en el proceso:", error);
  });

console.log("El programa sigue ejecutando otras tareas mientras las promesas esperan...");
Lenguaje del código: JavaScript (javascript)

Resultado en Consola:

Si ejecutas este código, verás los mensajes en este orden exacto:

  1. Iniciando búsqueda...
  2. El programa sigue ejecutando otras tareas mientras las promesas esperan... (¡Esto demuestra que no se bloquea!)
  3. (Pausa de 1 segundo) -> Usuario Alex encontrado con ID: 101
  4. (Pausa de 1 segundo) -> Se han recuperado 2 posts.
  5. Resultado final -> Título del post: Mi primer viaje a la playa

Explicación del flujo del código:

  1. La asincronía en acción:
    • Fíjate que el mensaje «El programa sigue ejecutando…» aparece antes que los resultados de las promesas. Esto sucede porque JavaScript delega las funciones setTimeout al navegador y continúa con la siguiente línea de código disponible.
  2. El primer .then():
    • Recibe el objeto usuario. Como necesitamos buscar sus posts, retornamos la ejecución de buscarPosts(101). Al retornar una promesa, el siguiente .then() se queda esperando automáticamente a que esa nueva promesa se resuelva.
  3. El segundo .then():
    • Este no se ejecuta hasta que los posts «llegan del servidor». Cuando llegan, recibe el array de posts. Aquí decidimos retornar solo un string (posts[0].titulo).
  4. El tercer .then():
    • Recibe directamente ese string. Como un string es un valor simple (sincrónico), este bloque se ejecuta casi instantáneamente después del anterior.
  5. Gestión de errores:
    • Si el usuario no existiera o el servidor de posts fallara, el código saltaría directamente al .catch(), ignorando todos los pasos intermedios.

4. Flujo de ejecución del encadenamiento

Primero debemos comprender esta regla:

JavaScript tiene un solo hilo de ejecución (Single Thread). Esto significa que literalmente solo puede ejecutar una línea de código a la vez. No puede hacer dos cosas simultáneamente.

Entonces, ¿cómo hace para dejar cosas en «segundo plano»? Pidiendo ayuda.

4.1. El flujo real paso a paso

Imagina que JavaScript es un mesero en un restaurante y el navegador (o Node.js) es la cocina.

1. Arranca la Promesa (El pedido)
Cuando llamas a una función que devuelve una promesa (por ejemplo, buscar datos en un servidor), JavaScript le dice al navegador: «Oye, encárgate de esta descarga de red, avísame cuando termines». JavaScript delega el trabajo pesado a las «Web APIs» del navegador (la cocina).
2. Mientras se espera (El mesero sigue trabajando)
Mientras el navegador está descargando los datos en ese «segundo plano», el mesero (JavaScript) no se queda de brazos cruzados esperando. El hilo principal continúa ejecutando el resto de tu código sincrónico. Si tienes más funciones debajo de la cadena de promesas, botones en tu web, o animaciones, todo eso sigue funcionando fluidamente.
3. La promesa se resuelve (¡El pedido está listo!)
El navegador termina la descarga y dice: «¡Listo!». Pero no interrumpe a JavaScript de golpe. En su lugar, toma el resultado y empuja la función que escribiste dentro del .then() a una sala de espera VIP llamada Microtask Queue (Cola de Microtareas).
4. El Event Loop entra en acción
El Event Loop es un vigilante que constantemente hace una sola pregunta: «¿El hilo principal (el mesero) ya terminó lo que estaba haciendo?». En cuanto el hilo principal se queda libre, el Event Loop toma tu .then() de la sala de espera y lo ejecuta, pasándole el resultado.

¿Qué pasa en el segundo .then()?

Aquí hay dos caminos, y en ambos el programa sigue a salvo:

  1. Camino A (Devuelves un valor común):
    • Retornas un texto o un número. Esto es instantáneo. JavaScript toma ese valor, crea una promesa ya resuelta y pone el siguiente .then() inmediatamente en la sala de espera. Se ejecutará casi en el mismo milisegundo.
  2. Camino B (Devuelves una NUEVA promesa):
    • Si el primer .then() retorna una nueva promesa (ej. procesar una imagen o hacer otra petición al servidor), el ciclo comienza de nuevo. JavaScript vuelve a delegar esta nueva tarea a la cocina (el navegador). La cadena de promesas se «pausa», pero tu aplicación en general no se pausa. El hilo principal queda libre de nuevo para reaccionar a clics del usuario u otros procesos hasta que esa segunda promesa termine.

EN RESUMEN:
Cada vez que hay una espera por una promesa, JavaScript «aparca» ese bloque de código .then() y se va a hacer otras cosas. Solo vuelve a ese punto exacto cuando el entorno (el navegador) le avisa que la tarea terminó. Nunca se queda congelado mirando el reloj.

5. Aplicando promesas con Fetch y el DOM

En el desarrollo web moderno, la API fetch es la reina indiscutible del encadenamiento de promesas.

Cuando usamos fetch para pedir datos a un servidor, casi siempre necesitamos manipular el DOM (la interfaz de usuario) para mostrar esos datos. Y como la red es impredecible (puede ser lenta o fallar), las promesas son vitales para no congelar la página.

Aquí tienes una estructura ideal, dividida en la vista (HTML) y la lógica (JavaScript), perfecta para explicar el concepto de principio a fin.

5.1. La estructura HTML (El DOM)

Necesitamos un lugar donde el usuario interactúe y un lugar donde mostrar la respuesta.

HTML

<div class="tarjeta-perfil">
  <button id="btn-cargar">Cargar Perfil de Usuario</button>
  <div id="contenedor-perfil">
    <p>Haz clic para buscar los datos.</p>
  </div>
</div>Lenguaje del código: HTML, XML (xml)

5.2. La lógica en JavaScript (aplicando encadenamiento)

Vamos a consultar una API pública y gratuita (JSONPlaceholder) para traer los datos de un usuario ficticio.

// Referencias al DOM
const boton = document.getElementById('btn-cargar');
const contenedor = document.getElementById('contenedor-perfil');

// Escuchamos el evento click
boton.addEventListener('click', () => {
  
  // 1. Damos feedback visual inmediato (El programa no se bloquea)
  contenedor.innerHTML = '<p>⏳ Conectando con el servidor...</p>';

  // 2. Iniciamos el encadenamiento de promesas
  fetch('https://jsonplaceholder.typicode.com/users/1')
    
    // Primer eslabón: Recibimos la "respuesta en bruto" del servidor
    .then((respuestaBruta) => {
      
      // Buenas prácticas: Validar que el servidor respondió con éxito (código 200)
      if (!respuestaBruta.ok) {
        throw new Error('No se pudo encontrar al usuario'); 
      }
      
      // EL SECRETO: el método .json() también devuelve una PROMESA.
      // Retornamos esta nueva promesa para extraer el cuerpo de los datos.
      return respuestaBruta.json(); 
    })
    
    // Segundo eslabón: Recibimos los datos ya transformados a un objeto JavaScript
    .then((usuario) => {
      
      // 3. Manipulamos el DOM con los datos reales
      contenedor.innerHTML = `
        <h3>👤 ${usuario.name}</h3>
        <p><strong>Email:</strong> ${usuario.email}</p>
        <p><strong>Empresa:</strong> ${usuario.company.name}</p>
      `;
    })
    
    // La red de seguridad: Atrapa errores de red o el error que lanzamos arriba
    .catch((error) => {
      
      // 4. Mostramos el error en la interfaz, no solo en la consola
      contenedor.innerHTML = `
        <p style="color: red;">❌ Ups, algo salió mal: ${error.message}</p>
      `;
    });
});
Lenguaje del código: JavaScript (javascript)

EXPLICACIÓN:

  1. El Feedback Visual Sincrónico:
    • Antes de lanzar el fetch, actualizamos el innerHTML a «⏳ Conectando…». Esto ocurre instantáneamente. Luego, el navegador se hace cargo de la petición de red en segundo plano, permitiendo que el usuario siga interactuando con la página si hubiera otros botones.
  2. El «Doble .then()» de Fetch:
    • Este es el punto donde muchos estudiantes se confunden al principio. La función fetch se resuelve tan pronto como los «encabezados» (headers) de la respuesta llegan del servidor, pero el cuerpo de los datos (el JSON) aún puede estar descargándose. Por eso respuestaBruta.json() es en sí misma una operación asíncrona y devuelve una nueva promesa que alimenta al siguiente .then().
  3. El throw new Error:
    • Las promesas de fetch solo saltan al .catch() si hay un fallo de red grave (ej. no hay internet). Si el servidor responde con un error 404 (No encontrado), fetch lo considera una conexión «exitosa». Por eso forzamos un error manual (throw) si la propiedad .ok es falsa, enviando el flujo directamente al .catch().

Deja un comentario

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