preloader

Encadenamiento de Promesas en JavaScript

Encadenamiento de Promesas

Introducción

En el capítulo anterior sobre callbacks, hablamos de cómo manejar una secuencia de tareas asincrónicas que deben ejecutarse una tras otra, como la carga de scripts. Ahora veremos cómo hacer esto de manera correcta utilizando promesas. En este capítulo, cubriremos el encadenamiento de promesas.

Ejemplo Básico

Considera el siguiente ejemplo de encadenamiento de promesas:

				
					new Promise(function(resolve, reject) {
  setTimeout(() => resolve(1), 1000); // (*)
}).then(function(result) { // (**)
  alert(result); // 1
  return result * 2;
}).then(function(result) { // (***)
  alert(result); // 2
  return result * 2;
}).then(function(result) {
  alert(result); // 4
  return result * 2;
});

				
			

Aquí, el resultado pasa a través de una cadena de manejadores .then.

  1. La promesa inicial se resuelve después de 1 segundo (*).
  2. El primer manejador .then (**), recibe el resultado, muestra una alerta con 1 y retorna result * 2.
  3. El siguiente .then (***) recibe el resultado anterior, muestra una alerta con 2, y retorna result * 2.
  4. El último .then recibe el resultado, muestra una alerta con 4 y retorna result * 2.

Cada llamada a .then devuelve una nueva promesa, lo que permite que el siguiente .then se encadene y reciba el resultado del anterior.

Un Error Común

Es posible agregar múltiples .then a una sola promesa, pero esto no es encadenamiento. Aquí hay un ejemplo de este error:

				
					let promise = new Promise(function(resolve, reject) {
  setTimeout(() => resolve(1), 1000);
});

promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});

promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});

promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});

				
			

En este caso, todos los manejadores .then reciben el mismo resultado y se ejecutan independientemente, mostrando siempre 1. El encadenamiento es mucho más útil y común en la práctica.

Devolviendo Promesas

Un manejador .then puede devolver una promesa, lo que permite encadenar acciones asincrónicas.

				
					new Promise(function(resolve, reject) {
  setTimeout(() => resolve(1), 1000);
}).then(function(result) {
  alert(result); // 1
  return new Promise((resolve, reject) => { // (*)
    setTimeout(() => resolve(result * 2), 1000);
  });
}).then(function(result) { // (**)
  alert(result); // 2
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(result * 2), 1000);
  });
}).then(function(result) {
  alert(result); // 4
});

				
			

En este código, cada .then devuelve una nueva promesa, que se resuelve después de un segundo, con el resultado multiplicado por 2. La salida es la misma que antes: 1 → 2 → 4, pero con un retraso de 1 segundo entre las alertas.

Ejemplo: Carga de Scripts

Veamos cómo encadenar promesas para cargar scripts uno por uno:

				
					loadScript("/article/promise-chaining/one.js")
  .then(function(script) {
    return loadScript("/article/promise-chaining/two.js");
  })
  .then(function(script) {
    return loadScript("/article/promise-chaining/three.js");
  })
  .then(function(script) {
    one();
    two();
    three();
  });

				
			

Este código puede simplificarse usando funciones de flecha:

				
					loadScript("/article/promise-chaining/one.js")
  .then(script => loadScript("/article/promise-chaining/two.js"))
  .then(script => loadScript("/article/promise-chaining/three.js"))
  .then(script => {
    one();
    two();
    three();
  });

				
			

Cada llamada a loadScript devuelve una promesa, y el siguiente .then se ejecuta cuando la promesa se resuelve.

Objetos Thenables

Un manejador .then puede devolver un objeto llamado “thenable”, que es un objeto con un método .then.

				
					class Thenable {
  constructor(num) {
    this.num = num;
  }
  then(resolve, reject) {
    alert(resolve); // function() { código nativo }
    setTimeout(() => resolve(this.num * 2), 1000); // (**)
  }
}

new Promise(resolve => resolve(1))
  .then(result => {
    return new Thenable(result); // (*)
  })
  .then(alert); // muestra 2 después de 1000 ms

				
			

JavaScript reconoce los objetos thenables y los trata como promesas, permitiendo integrarlos en cadenas de promesas.

Ejemplo Completo: Fetch

En programación frontend, las promesas son útiles para solicitudes de red. Veamos un ejemplo utilizando fetch para cargar información de usuario desde un servidor:

				
					fetch('/article/promise-chaining/user.json')
  .then(response => response.json())
  .then(user => fetch(`https://api.github.com/users/${user.name}`))
  .then(response => response.json())
  .then(githubUser => {
    let img = document.createElement('img');
    img.src = githubUser.avatar_url;
    img.className = "promise-avatar-example";
    document.body.append(img);

    setTimeout(() => img.remove(), 3000); // (*)
  });

				
			

Para hacer que la cadena sea extensible, devolvemos una promesa que se resuelve cuando el avatar termina de mostrarse:

				
					fetch('/article/promise-chaining/user.json')
  .then(response => response.json())
  .then(user => fetch(`https://api.github.com/users/${user.name}`))
  .then(response => response.json())
  .then(githubUser => new Promise(function(resolve, reject) { // (*)
    let img = document.createElement('img');
    img.src = githubUser.avatar_url;
    img.className = "promise-avatar-example";
    document.body.append(img);

    setTimeout(() => {
      img.remove();
      resolve(githubUser); // (**)
    }, 3000);
  }))
  .then(githubUser => alert(`Terminado de mostrar ${githubUser.name}`));

				
			

Finalmente, podemos dividir el código en funciones reutilizables:

 
				
					function loadJson(url) {
  return fetch(url)
    .then(response => response.json());
}

function loadGithubUser(name) {
  return loadJson(`https://api.github.com/users/${name}`);
}

function showAvatar(githubUser) {
  return new Promise(function(resolve, reject) {
    let img = document.createElement('img');
    img.src = githubUser.avatar_url;
    img.className = "promise-avatar-example";
    document.body.append(img);

    setTimeout(() => {
      img.remove();
      resolve(githubUser);
    }, 3000);
  });
}

loadJson('/article/promise-chaining/user.json')
  .then(user => loadGithubUser(user.name))
  .then(showAvatar)
  .then(githubUser => alert(`Finished showing ${githubUser.name}`));

				
			

Resumen

Si un manejador .then (o catch/finally) devuelve una promesa, el resto de la cadena esperará hasta que esta se resuelva o rechace. El resultado o error pasará al siguiente manejador en la cadena.

Related Post

Deja una respuesta

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