Node.js Promise

Promise es uno de los conceptos centrales para manejar operaciones asíncronas en Node.js.

Promise proporciona una forma más elegante de gestionar código asíncrono, evitando el problema clásico del "Callback Hell" (infierno de callbacks).

Este artículo detalla el concepto, el uso y los patrones comunes de Promise.


¿Qué es Promise?

Promise es un objeto que representa la finalización o el fallo eventual de una operación asíncrona. Tiene tres estados:

  1. Pending (pendiente): estado inicial, ni exitoso ni fallido
  2. Fulfilled (cumplido): la operación se completó con éxito
  3. Rejected (rechazado): la operación falló

Una vez que el estado de un Promise cambia (de pending a fulfilled o rejected), no vuelve a cambiar.


Crear un Promise

En Node.js, se puede utilizar elPromiseconstructor para crear un nuevo objeto Promise:

Ejemplo

const myPromise = new Promise((resolve, reject) => {
  // operación asíncrona
  const success = true; // supongamos que este es el resultado de la operación asíncrona
 
  if (success) {
    resolve('¡operación exitosa!'); // el estado cambia a fulfilled
  } else {
    reject('¡operación fallida!'); // el estado cambia a rejected
  }
});

Usando Promise

Promise proporciona los.then()y.catch()métodos para manejar casos de éxito y de fallo:

Ejemplo

myPromise
  .then((result) => {
    console.log(result); // salida: "¡operación exitosa!"
  })
  .catch((error) => {
    console.error(error); // salida: "¡operación fallida!"
  });

Cadena de promesas (Promise chaining)

El poder de Promise radica en poder encadenar múltiples operaciones asíncronas:

Ejemplo

function asyncOperation1() {
  return new Promise((resolve) => {
    setTimeout(() => resolve('primer paso completado'), 1000);
  });
}

function asyncOperation2(data) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(`${data},segundo paso completado`), 1000);
  });
}

asyncOperation1()
  .then((result) => asyncOperation2(result))
  .then((finalResult) => {
    console.log(finalResult); // salida: "primer paso completado, segundo paso completado"
  })
  .catch((error) => {
    console.error('Error en la cadena:', error);
  });

Métodos estáticos de Promise

Promise ofrece algunos métodos estáticos útiles:

Promise.all()

Espera a que todos los Promise se completen, o a que cualquier Promise falle:

Ejemplo

const promise1 = Promise.resolve('primero');
const promise2 = Promise.resolve('segundo');

Promise.all([promise1, promise2])
  .then((results) => {
    console.log(results); // salida: ['primero', 'segundo']
  });

Promise.race()

Devuelve el Promise que se complete o falle primero:

Ejemplo

const promise1 = new Promise((resolve) => setTimeout(resolve, 500, 'primero'));
const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 'segundo'));

Promise.race([promise1, promise2])
  .then((result) => {
    console.log(result); // salida: "segundo"
  });

Manejo de errores

El manejo de errores de Promise se puede implementar mediante.catch()o.then()el segundo parámetro:

Ejemplo

someAsyncFunction()
  .then(
    (result) => { /* manejar éxito */ },
    (error) => { /* manejar fallo */ }
  );

// o
someAsyncFunction()
  .then((result) => { /* manejar éxito */ })
  .catch((error) => { /* manejar todos los errores */ });

Sintaxis async/await

ES2017 introdujo la sintaxis async/await, haciendo el uso de Promise más intuitivo:

Ejemplo

async function runOperations() {
  try {
    const result1 = await asyncOperation1();
    const result2 = await asyncOperation2(result1);
    console.log(result2);
  } catch (error) {
    console.error(error);
  }
}

runOperations();

Mejores prácticas

  1. Siempre maneja los errores: no ignores.catch()o try-catch
  2. Evita el anidamiento: usa encadenamiento o async/await para mantener el código plano
  3. Nombra los Promise: asigna nombres significativos a las variables Promise
  4. Retorna Promise: retorna Promise en las funciones para permitir el encadenamiento
Otras extensiones