Cómo usar Promise.all() para esperar varias promesas

Cómo usar Promise.all() para esperar una serie de llamadas de Promise para resolver antes de ejecutar más código.

Para este tutorial, vamos a utilizar el marco JavaScript de pila completa de CheatCode, Joystick. Joystick reúne un marco de interfaz de usuario de front-end con un back-end de Node.js para crear aplicaciones.

Para comenzar, querremos instalar Joystick a través de NPM. Asegúrese de estar usando Node.js 16+ antes de instalar para garantizar la compatibilidad (lea este tutorial primero si necesita aprender a instalar Node.js o ejecutar varias versiones en su computadora):

Terminal

npm i -g @joystick.js/cli

Esto instalará Joystick globalmente en su computadora. Una vez instalado, vamos a crear un nuevo proyecto:

Terminal

joystick create app

Después de unos segundos, verá un mensaje desconectado de cd en su nuevo proyecto y ejecute joystick start :

Terminal

cd app && joystick start

Después de esto, su aplicación debería estar ejecutándose y estamos listos para comenzar.

Escribiendo una Promesa de prueba

Si bien técnicamente podemos usar cualquier Promesa de JavaScript para nuestra prueba, para simplificar las cosas, comenzaremos por conectar una Promesa muy simple:una función que toma un tiempo de espera de timeoutInSeconds y resuelve una Promesa devuelta por la función en un setTimeout() después de que se complete el tiempo de espera.

/lib/wait.js

export default (timeoutInSeconds = 0) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(`Completed after ${timeoutInSeconds} seconds!`);
    }, timeoutInSeconds * 1000);
  });
};

Aquí, dentro del /lib carpeta que se creó para nosotros cuando ejecutamos joystick create app arriba, creamos un nuevo archivo wait.js y en él, agrega un export default de una función que toma timeoutInSeconds como único argumento (el = 0 parte después del nombre del argumento somos nosotros estableciendo un valor predeterminado de 0 en caso de que no se pase ningún valor).

Dentro de esa función, devolvemos un new Promise() . A ese new Promise() Por ejemplo, pasamos una función de devolución de llamada para definir el comportamiento de nuestra Promesa. Como insinuamos anteriormente, queremos ejecutar un setTimeout() , usando el timeoutInSeconds nos pasó como el retraso para el setTimeout() .

Dentro de la devolución de llamada para ese setTimeout() , después del timeoutInSeconds se ha completado, llamamos al resolve función que se nos pasa cuando llamamos a new Promise() . A él, le pasamos una cadena reconociendo que completamos la "espera" solicitada para que obtengamos un valor de retorno al llamar a la Promesa a través de Promise.all() .

Eso es todo para nuestra Promesa de prueba. Nuevamente, puede usar cualquier función que devuelva una Promesa para el siguiente paso (por ejemplo, una llamada a fetch() o alguna función de biblioteca de terceros que devuelve una Promesa).

Usando Promise.all()

Ahora, a ver cómo Promise.all() funciona, vamos a crear un componente simple en nuestra aplicación usando el @joystick.js/ui paquete integrado en el marco que estamos usando para este tutorial. Para simplificar las cosas, vamos a abrir el archivo en /ui/pages/index/index.js que se creó para nosotros cuando ejecutamos joystick create app más temprano. Para empezar, vamos a reemplazar el contenido de este archivo con la siguiente plantilla:

/ui/pages/index/index.js

import ui from '@joystick.js/ui';

const Index = ui.component({
  render: ({ state }) => {
    return `
      <div></div>
    `;
  },
});

export default Index;

Esto nos dará un componente de pizarra en blanco para probar nuestro Promise.all() Llamada de. A continuación, modifiquemos este componente para agregar nuestro código de prueba (extrayendo nuestro /lib/wait.js en la parte superior del archivo) y explica cómo funciona.

/ui/pages/index/index.js

import ui from '@joystick.js/ui';
import wait from '../../../lib/wait';

const Index = ui.component({
  state: {
    running: false,
  },
  events: {
    'click button': (_event, component) => {
      component.setState({ running: true }, async () => {
        const results = await Promise.all([
          wait(1),
          wait(2),
          wait(4),
          wait(8),
        ]);
  
        console.log(results);
        component.setState({ running: false });
      });
    },
  },
  render: ({ state }) => {
    return `
      <div>
        <button ${state?.running ? 'disabled' : ''}>${!state?.running ? 'Start the Promise chain' : 'Running...'}</button>
      </div>
    `;
  },
});

export default Index;

Empezando por el render() , hemos agregado un <button></button> que podemos hacer clic para iniciar nuestra cadena Promise. Para decidir si está deshabilitado o no (y qué lee su etiqueta actual), "arrancamos" el state actual del componente valor al desestructurar el objeto de instancia del componente pasado a render() .

Si el running el valor es verdadero, queremos marcar el botón como disabled por lo que no se puede hacer clic y cambiar su etiqueta a "En ejecución..." Si miramos cerca de la parte superior de nuestra llamada a ui.component() , la primera propiedad que establecemos en el objeto de opciones pasado a esta función (la definición de nuestro componente) es un objeto state y dentro de una propiedad running siendo establecido en false . Somos nosotros configurando el state predeterminado valor para el componente cuando se carga en el navegador (es decir, el botón no desactivarse cuando se cargue la página).

A continuación, arriba en el events objeto que se define debajo de state , agregamos un detector de eventos para un click evento en nuestro <button></button> con click button . A esa propiedad, le pasamos una función para llamar cuando un click se detecta en el botón. Dentro, usando el component instancia pasada como segundo argumento al controlador de eventos (después del objeto de evento DOM en sí mismo que podemos ignorar aquí), llamamos al .setState() del componente método para establecer running a true .

Después de esto, pasamos una función de devolución de llamada (tenga en cuenta el prefijo de async palabra clave en esta función) para llamar después del setState() la llamada se ha completado. Dentro, creamos una variable const results al que se le asigna una llamada a await Promise.all() . Para Promise.all() , estamos pasando una serie de llamadas a nuestro wait() función que hemos importado en la parte superior del archivo.

Recuerda:nuestro wait() la función recibe un número entero que representa un timeoutInSeconds queremos que nuestra función espere antes de resolverse. Porque nuestro objetivo aquí es demostrar una llamada a Promise.all() , queremos llamar a wait() varias veces con diferentes tiempos de espera. Esto demostrará retrasos en el mundo real de otras funciones basadas en Promise que debemos esperar antes de completar alguna tarea. Aquí, esperamos el Promise.all() llama a no resolver hasta todas de las Promesas pasadas a resolver.

En otras palabras, esperamos hacer clic en el botón y tener un retraso de ocho (8) segundos hasta Promise.all() resuelve y almacena su resultado en el results variable. La idea aquí es que, aunque tenemos un console.log() de results y otra llamada al component.setState() para habilitar nuestro <button></button> de nuevo, no esperamos que se llamen hasta Promise.all() se resuelve después de 8 segundos.

Si cargamos esto en un navegador y hacemos clic en nuestro botón, deberíamos ver este resultado exacto, con results que contiene una matriz de cadenas, cada una de las cuales representa el valor de retorno pasado a resolve() dentro de nuestro wait() función.

Terminando

En este tutorial, aprendimos a usar Promise.all() para esperar en una serie de Promesas. Aprendimos a definir una Promesa de JavaScript simple que espera algún timeoutInSeconds para completar antes de resolver, y luego, dentro de un componente Joystick, cómo conectar un detector de eventos para llamar a Promise.all() —pasando múltiples wait() llamadas con diferentes tiempos de espera, esperando que se resuelva y nos devuelva el results de cada llamada.