El concepto de código asíncrono puede parecer bastante complicado, especialmente para los desarrolladores novatos. Afortunadamente, las promesas de JavaScript hacen que lidiar con este tipo de situaciones sea mucho más fácil.

“GESTIONAR LA COMPLEJIDAD ES LA QUINTA ESENCIA DE LA PROGRAMACIÓN.” – BRIAN KERNIGHAN

Como dijo Brian: la esencia de la programación es la capacidad de los desarrolladores para hacer frente a situaciones difíciles. Para alcanzar la excelencia profesional, debemos dominar audazmente conceptos difíciles como el descrito anteriormente.

En este artículo, consideraremos el concepto de promesas y su propósito. Además, descubriremos por qué es tan importante encadenar promesas y cómo el método Promise.all()ayuda a combinar varias promesas en una sola.

¡Adelante por el conocimiento!

¿Qué es una promesa?

Una promesa es un objeto de JavaScript que maneja código asíncrono. Le permiten aplazar bloques de código hasta que la acción se ejecute o rechace. En lugar de pasar devoluciones de llamada a una función, se adjuntan al final de la promesa, lo que proporciona una mejor legibilidad y encadenabilidad.

No deben confundirse con async/await.

Para comprender completamente cómo funcionan las promesas, primero veamos el ejemplo de devolución de llamada y luego pasemos a una descripción general de otros casos.

const DEFAULT_TIMEOUT = 500;

function getMovieTicket(movie,fulfillMovieTicket,rejectMovieTicket){
  setTimeout(() => {
    if(movie.payment >= movie.ticket_price){
      fulfillMovieTicket(`Success! Payment has been processed.`);
    }else{
      rejectMovieTicket(`Error: Payment less than ticket price.`);
    }
  },300) 
}

function selectMovie(selection,selectedMovie,rejectMovieSelection){
  setTimeout(() => {
    if(selection.time_taken <= DEFAULT_TIMEOUT){
     selectedMovie(selection.title)
    }else{
      rejectMovieSelection(`Your session has expired.`)
    }
  },DEFAULT_TIMEOUT)
}

const movie_obj = {
  payment: 21.25,
  ticket_price: 21.00,
  title: 'Adventures of Pickle Rick',
  time_taken: 200
}

selectMovie(movie_obj,(movie) => {
  console.log(`Movie selected: ${movie}`)
  getMovieTicket(movie_obj,(response) => {
    console.log(response);
  },(err) => {
    console.error(err);
  })
},(err) => {
  console.error(err);
})

Este es un ejemplo de un quiosco de boletos de cine que usa devoluciones de llamada para procesar código asíncrono. Ambos selectMovie()getMovieTicket()pasan 2 devoluciones de llamada como argumentos. La primera función se llama en caso de una transacción exitosa y la segunda, sin éxito.

Dado que selectMovie() y getMovieTicket() ambas funciones son asincrónicas, las devoluciones de llamada se utilizan para diferir la ejecución de otras líneas de código hasta que finaliza el proceso.

Para que este ejemplo funcione correctamente, debe asegurarse de que las funciones consideradas se ejecuten en el orden correcto para evitar un resultado erróneo, ya que estos casos no son infrecuentes cuando se interactúa con código asíncrono.

Reescribamos el ejemplo anterior con promesas y veamos qué ha cambiado:

const DEFAULT_TIMEOUT = 500;

const getMovieTicket = (movie) => 
  new Promise((resolve,reject) => {
    setTimeout(() => {
      if(movie.payment >= movie.ticket_price){
        resolve(`Success! Payment has been processed.`);
      }else{
        reject(`Error: Payment less than ticket price.`);
      }
    },300)
});

const selectMovie = (selection) => 
  new Promise((resolve,reject) => {
    setTimeout(() => {
      if(selection.time_taken <= DEFAULT_TIMEOUT){
        resolve(selection.title)
      }else{
        reject(`Your session has expired.`)
      }
    },DEFAULT_TIMEOUT)
});

const movie_obj = {
  payment: 21.25,
  ticket_price: 21.00,
  title: 'Adventures of Pickle Rick',
  time_taken: 200
}

selectMovie(movie_obj).then(movie => {
  console.log(`Movie selected: ${movie}`)
  getMovieTicket(movie_obj).then(response => {
    console.log(response);
  }).catch(err => {
   console.error(err);
  })
}).catch(err => {
 console.error(err);
})

Como puede ver, esta implementación no es muy diferente de la anterior. En lugar de pasar devoluciones de llamada a funciones, devolvemos una promesa, lo que nos permite adjuntar devoluciones de llamada al final de la promesa a través de los métodos then()catch().

ES IMPORTANTE TENER EN CUENTA QUE LAS PROMESAS SE EJECUTAN DE FORMA SÍNCRONA Y SOLO SE VUELVEN ASÍNCRONAS DENTRO DE LOS MÉTODOS THEN()CATCH()

La separación de dos métodos asincrónicos cambiará la salida. El caso es que ninguna de las promesas espera a que la otra se cumpla, ya que ahora son independientes entre sí. Esta es la diferencia entre las promesas y async/await en JavaScript.

getMovieTicket(movie_obj).then(response => {
  console.log(response);
}).catch(err => {
  console.error(err);
})

selectMovie(movie_obj).then(movie => {
 console.log(`Movie selected: ${movie}`) 
}).catch(err => {
 console.error(err);
})

Echemos un vistazo más de cerca a cómo funcionan estos dos métodos.

Promesa.then()

El método then()devuelve una promesa. En caso de éxito, then()llama a una función que devuelve el valor recibido. En este caso, es el valor pasado a la devolución de llamada resolve()en el ejemplo anterior.

Promise.catch()

De manera similar then(), el método catch()también devuelve una promesa, pero solo si es rechazada. Tal vez lo sepa por su experiencia con el try-catch. Si la promesa es rechazada, catch()llama a una función que devuelve el motivo del error.

Veámoslo Promise.catch()en acción:

const movie_obj = {
  payment: 19.00,
  ticket_price: 21.00,
  title: 'Adventures of Pickle Rick',
  time_taken: 200
}

selectMovie(movie_obj).then(movie => {
  console.log(`Movie selected: ${movie}`)
  getMovieTicket(movie_obj).then(response => {
   console.log(response);
  }).catch(err => {
   console.error(err);
  })
}).catch(err => {
 console.error(err);
})

Dado que el pago es menor que el precio del boleto, el motivo del rechazo de la promesa se pasa a la devolución de llamada rejected(), lo que da como resultado el resultado anterior.

Pero, ¿Qué sucede si necesita mostrar un recibo por el hecho de un pago realizado?

Veamos cómo hacer esto encadenando promesas.

Combinando promesas en cadenas

La esencia del procedimiento es simple. Si necesita registrar un recibo de pago, simplemente adjunte un método then()al final del anterior.

Considere el siguiente código y vea cómo funciona:

selectMovie(movie_obj).then(movie => {
  console.log(`Movie selected: ${movie}`)
  getMovieTicket(movie_obj).then(response => {
    console.log(response);
    return response
  }).then(details => {
    const receipt = 'Keep this as your receipt | ' + details
    console.log(receipt);
  })
  .catch(err => {
    console.error(err);
  })
}).catch(err => {
  console.error(err);
})

Para que el código funcione correctamente, debe asegurarse de que devolvamos el valor del primero then(); de lo contrario, el segundo método devolverá undefined. Puede encadenar tantos métodos como desee entre sí.

¡No hay restricciones!

Aunque este código funciona correctamente, en situaciones reales, la salida del recibo también será una acción asíncrona. Por lo tanto, debe ponerlo en una promesa separada como esta:

const printReceipt = (payment_details, status) =>
  new Promise((resolve,reject) => {
    setTimeout(() => {
     if(status === 'OK'){
       resolve('Keep this as your receipt | ' + payment_details)
      }else{
       reject('Error: The printer has encountered a jam.')
      }
    },300)
  })

En este fragmento de código, estamos comprobando si la impresora está saliendo en OKel estado previo a la promesa. Si encuentra un error, envíe una promesa rechazada y registre el error en la consola.

Si sumamos esta promesa al resto, obtenemos el siguiente resultado:

selectMovie(movie_obj).then(movie => {
  console.log(`Movie selected: ${movie}`)
  getMovieTicket(movie_obj).then(response => {
    console.log(response);
    printReceipt(response,'OK').then(receipt => {
     console.log(receipt);
    }).catch(err => {
     console.error(err);
    })
  })
  .catch(err => {
   console.error(err);
  })
}).catch(err => {
 console.error(err);
})

En términos de legibilidad, esta opción se ve peor.

Hay un nombre especial para esta situación: "infierno de devolución de llamada". Esto sucede cuando se ejecutan varias promesas una tras otra para completar el proceso de procesamiento asíncrono.

¡Puedes arreglar esto encadenando promesas!

Es esta solución la que ayuda a lidiar efectivamente con tales situaciones. Veamos el siguiente ejemplo:

selectMovie(movie_obj).then(movie => {
  console.log(`Movie selected: ${movie}`)
  return getMovieTicket(movie_obj); 
})
.then(response => {
  console.log(response);
  return printReceipt(response,'OK')
})
.then(receipt => {
  console.log(receipt)
})
.catch(err => {
  console.error(err);
})

¡Mucho mejor!

No solo redujimos el número de líneas de código, sino que también lo hicimos más legible.

Solo era necesario poner todo en los estantes, ya que el complejo se volvió simple. Después de ejecutar cada promesa, devolvemos la siguiente promesa en la secuencia. Este proceso continúa hasta que se completan todas las acciones.

Por el momento, no solo pusimos el código en orden, sino que también resolvimos un par de problemas en el camino. Sin embargo, existe otro método que permite mayores reducciones.

Casos de uso para Promise.all()

Como puedes ver en los ejemplos anteriores, selectMovie()getMovieTicket()no dependes del resultado de otra promesa. Son ideales para casos con Promise.all().

¿Qué es Promise.all()? 

Este método toma una matriz de promesas y devuelve una sola promesa que contiene una matriz de resultados de entrada. Esta promesa finaliza solo después de que se hayan cumplido todas las promesas de entrada. Si alguno de ellos es rechazado, también será rechazado.

El siguiente ejemplo le ayudará a comprender cómo funciona el método Promise.all():

const promise1 = () => 
  new Promise((resolve,reject) => {
   setTimeout(() => {
     resolve('Promise 1 checking in.')
    },200)   
  })
  
 const promise2 = () => 
  new Promise((resolve,reject) => {
    setTimeout(() => {
     resolve('Promise 2 checking in.')
    },400)
  })
 
 const promise3 = () => 
  new Promise((resolve,reject) => {
    setTimeout(() => {
     resolve('Promise 1 checking in.')
    },100)
  })
  
  Promise.all([promise1(),promise2(),promise3()])
  .then(([p1,p2,p3]) => {
   console.log(`${p1}\n${p2}\n${p3}`)
  })
 

Promise.all()acepta las 3 promesas como entrada. Tan pronto como se ejecutan, la matriz de valores resultante se desestructura y el resultado se escribe en el registro.

A pesar de la aparente complejidad, todo es más fácil de lo que piensas. En base al conocimiento ya existente sobre el funcionamiento del método, lo implementamos en el código de una entrada de cine:

Promise.all([selectMovie(movie_obj),getMovieTicket(movie_obj)])
.then(([movie,response]) => {
  console.log(`Movie selected: ${movie}`)
  console.log(response);
  return printReceipt(response,'OK')
})
.then(receipt => {
  console.log(receipt)
})
.catch(err => {
  console.error(err);
})

¡Multa!

El método Promise.all()nos permitió limpiar el código y reducirlo a unas pocas líneas. Además, también podemos usarlo en cadenas. Muy cómodamente.

Promise.all()espera a que se realice la selección y el pago antes de retirar el recibo. Surge la pregunta: “¿Por qué no lanzar ahí la tercera promesa para la empresa?”. El hecho es que la acción con el recibo depende del resultado del pago, por lo que primero debe esperar el cumplimiento de esta promesa y luego pasar a la siguiente etapa. Por lo tanto, agregamos la promesa de recibo al resultado Promise.all().

Ahora, después de leer el material del artículo, puede dominar el trabajo con código asíncrono utilizando promesas de JavaScript.

Conclusión

Resumamos brevemente.

Las promesas de JavaScript lo ayudan a escribir código limpio y legible, así como a resolver problemas comunes como el infierno de devolución de llamadas.

Las promesas manejan código asincrónico, por lo que deben usarse siempre que sea posible. El encadenamiento de promesas ayuda a simplificar el código, hacerlo legible y eliminar la complejidad excesiva.

El método Promise.all()combina varias promesas en una y optimiza aún más el código. También se puede agregar al código existente que usa cadenas.

El papel de las promesas en el procesamiento de código asíncrono es uno de los temas de discusión más interesantes. Es un placer trabajar con ellos todos los días.

Compartir:
Categorías: Programación

0 comentarios

Deja una respuesta

Marcador de posición del avatar

Tu dirección de correo electrónico no será publicada.

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.