javascript-promesas

Promesas en JavaScript

En JavaScript las Promesas son un mecanismo fundamental para facilitar la programación asíncrona.

Nos permiten gestionar operaciones que no se completan inmediatamente (como solicitudes de red, operaciones de lectura de archivos o temporizadores) de una manera más estructurada y menos propensa a errores en comparación con los callbacks.

Una promesa es como un “contenedor” para un valor que puede estar disponible ahora, en el futuro, o nunca (por eso se llama promesa)

En general, es aconsejable usar de async/await para trabajar con promesas de manera más sincrónica. Esto puede hacer que el código asíncrono sea más fácil de entender y mantener.

Funcionamiento de una promesa

Una promesa es un objeto que representa el posible éxito o fallo de una operación asíncrona.

Para reflejar esto, durante su ejecución la promesa tiene una propiedad Estado, que puede ser uno de los siguientes:

  • Pendiente (Pending): Estado inicial. La operación asíncrona aún no se ha completado.
  • Cumplida (Fulfilled): La operación se ha completado con éxito y la promesa tiene un valor resultante.
  • Rechazada (Rejected): La operación falló y la promesa tiene un motivo de error.

Creación de una promesa

Podemos crear una promesa utilizando el constructor Promise. Este constructor toma una función (llamada executor) que recibe dos funciones como argumentos:

  • resolve se llama si la operación es exitosa, y se cambia el estado de la promesa a cumplida.
  • reject se llama si hay un error, y se cambia el estado a rechazada.

Estos argumentos son funciones que se llaman para cambiar el estado de la promesa. Vamos a verlo con un ejemplo,

const miPromesa = new Promise((resolve, reject) => {
  // Simulación de una operación asíncrona
  let exito = true; // Simula el resultado de la operación

  if (exito) {
    resolve('Operación exitosa');
  } else {
    reject('Error en la operación');
  }
});

En este ejemplo,

  • Creamos una promesa que recibe sus dos funciones resolve y reject.
  • Simulamos hacer una operación cualquiera con exito.
  • En función del resultado de la operación, invocamos a resolve o a reject.

Manejo de promesas

Para manejar el resultado de una promesa, normalmente utilizamos los métodos .then() y .catch():

miPromesa.then(onFulfilled, onRejected)

Este método se llama cuando la promesa se cumple o se rechaza.

  • onFulfilled es una función que se llama con el valor de la promesa cuando se cumple,
  • onRejected se llama con el motivo del rechazo si la promesa falla.
miPromesa.catch(onRejected)

Este método se utiliza para manejar errores. Es un atajo para .then(null, onRejected) y se llama cuando la promesa es rechazada.

Por ejemplo, vamos a ver un ejemplo de como se usaría una promesa normalmente,

miPromesa
  .then(result => {
    console.log(result); // 'Operación exitosa'
  })
  .catch(error => {
    console.error(error); // 'Error en la operación'
  });

Encadenamiento de promesas

Las promesas permiten encadenar múltiples .then() para manejar secuencias de operaciones asíncronas.

Cada .then() devuelve una nueva promesa, lo que facilita la composición de tareas asíncronas.

miPromesa
  .then(result => {
    console.log(result); // 'Operación exitosa'
    return 'Otro valor';
  })
  .then(otroResultado => {
    console.log(otroResultado); // 'Otro valor'
  })
  .catch(error => {
    console.error(error);
  });

Promesas anidadas

También podríamos anidar promesas dentro de otras promesas para gestionar operaciones que dependen de resultados anteriores.

miPromesa
  .then(result => {
    console.log(result); // 'Operación exitosa'
    return new Promise((resolve, reject) => {
      setTimeout(() => resolve('Operación asíncrona adicional'), 1000);
    });
  })
  .then(adicional => {
    console.log(adicional); // 'Operación asíncrona adicional'
  })
  .catch(error => {
    console.error(error);
  });

Sin embargo, esto puede llevar a código difícil de leer, por lo que es preferible usar encadenamiento siempre que sea posible.

Métodos estáticos de promesas

JavaScript proporciona varios métodos estáticos en el objeto Promise para manejar múltiples promesas simultáneamente.

Estos métodos devuelven una nueva promesa, que es una composición de las promesas que le pasamos como argumentos.

Promise.all()

Promise.all() recibe un iterable de promesas y devuelve una nueva promesa que se resuelve cuando todas las promesas del iterable se cumplen. Si alguna promesa es rechazada, la promesa devuelta será rechazada inmediatamente.

const promesa1 = Promise.resolve(1);
const promesa2 = Promise.resolve(2);
const promesa3 = Promise.resolve(3);

Promise.all([promesa1, promesa2, promesa3])
  .then(results => {
    console.log(results); // [1, 2, 3]
  })
  .catch(error => {
    console.error(error);
  });

Promise.race()

Promise.race() devuelve una promesa que se resuelve o se rechaza tan pronto como una de las promesas en el iterable se resuelve o se rechaza.

const promesaLenta = new Promise(resolve => setTimeout(() => resolve('Lenta'), 2000));
const promesaRápida = new Promise(resolve => setTimeout(() => resolve('Rápida'), 1000));

Promise.race([promesaLenta, promesaRápida])
  .then(result => {
    console.log(result); // 'Rápida'
  });

Promise.allSettled()

Promise.allSettled() devuelve una promesa que se resuelve cuando todas las promesas del iterable han terminado (ya sea que se hayan cumplido o rechazado)

const promesa1 = Promise.resolve(1);
const promesa2 = Promise.reject('Error');
const promesa3 = Promise.resolve(3);

Promise.allSettled([promesa1, promesa2, promesa3])
  .then(results => {
    console.log(results);
    // [{status: 'fulfilled', value: 1}, {status: 'rejected', reason: 'Error'}, {status: 'fulfilled', value: 3}]
  });

Devuelve un array de objetos que describen el resultado de cada promesa.

Promise.any()

Promise.any() devuelve una promesa que se resuelve tan pronto como una de las promesas en el iterable se resuelve.

const promesa1 = Promise.reject('Error 1');
const promesa2 = Promise.reject('Error 2');
const promesa3 = Promise.resolve('Éxito');

Promise.any([promesa1, promesa2, promesa3])
  .then(result => {
    console.log(result); // 'Éxito'
  });

Si todas las promesas son rechazadas, la promesa devuelta será rechazada con una AggregateError.

Ejemplos prácticos