Introducción a Promises en JavaScript

Una promesa en JavaScript es un objeto que puede producir un valor único al finalizar (o fallar) una operación asincrónica. Funciona como un proxy de un valor no necesariamente conocido en el momento en que se creó la promesa. Las promesas le permiten adjuntar controladores de devolución de llamada para manejar el futuro valor de éxito asincrónico o el motivo de falla.

¿Qué es una devolución de llamada?

Dado que JavaScript es un lenguaje de programación asíncrono de un solo subproceso, se utilizan funciones de devolución de llamada para que funcione como un lenguaje de programación asíncrono. Aquí hay un ejemplo de setTimeout() devolución de llamada de función:

setTimeout(() => {
  console.log('I waited 2 seconds.');
}, 2000);

En el ejemplo anterior, setTimeout() espera dos segundos y luego llama a la función que le pasamos. Esta función se denomina función de devolución de llamada. Entonces, las devoluciones de llamada son básicamente solo el nombre de una convención para usar funciones de JavaScript.

Las devoluciones de llamada son buenas desde una perspectiva de rendimiento. A diferencia de la mayoría de las funciones que devuelven inmediatamente algún valor, las funciones con devoluciones de llamada tardan un tiempo en producir un resultado. Por lo general, las devoluciones de llamadas se utilizan para tareas que consumen mucho tiempo, como descargar archivos, leer archivos, enviar correos electrónicos, obtener datos de una base de datos, etc.

Infierno de devolución de llamada

Ahora imaginemos un escenario en el que desea esperar otros dos segundos después de que finalice la primera devolución de llamada y hacer algo. Su código tendrá el siguiente aspecto:

setTimeout(() => {
  console.log('I waited 2 seconds.');
  setTimeout(() => {
    console.log('I waited another 2 seconds.');
  }, 2000);
}, 2000);

Y ahora, si desea hacer algo después de que finalice la segunda devolución de llamada, obtendrá otra devolución de llamada anidada:

setTimeout(() => {
  console.log('I waited 2 seconds.');
  setTimeout(() => {
    console.log('I waited another 2 seconds.');
    setTimeout(() => {
        console.log('I waited a total of 6 seconds.');
    }, 2000);
  }, 2000);
}, 2000);

Las devoluciones de llamadas anidadas (funciones dentro de funciones) hacen que sea diferente mantener y escalar el código. En el código anterior, tenemos tres niveles de funciones anidadas, una para cada setTimeout() llamar. Tener una aplicación con decenas de devoluciones de llamada anidadas hará que los desarrolladores vivan un infierno para actualizar o incluso comprender el código. Tal situación se conoce como infierno de devolución de llamada .

Ahí es donde las promesas de JavaScript son útiles.

Promesas de JavaScript

Las promesas en JavaScript son muy similares a las promesas que haces en tu vida diaria, una especie de garantía de que algo se hará en el futuro. Una promesa de JavaScript es un objeto que se puede devolver de forma síncrona desde una función asíncrona.

Las promesas no pretenden reemplazar las devoluciones de llamada. En cambio, simplifican el encadenamiento de funciones, lo que facilita la lectura y el mantenimiento del código. Una promesa puede estar en uno de los siguientes estados:

  • fulfilled - La acción asociada con la promesa se completó con éxito.
  • rejected - La acción relacionada con la promesa falló.
  • pending - Estado inicial, ni cumplido ni rechazado.
  • settled - La promesa no está pendiente (ni cumplida ni rechazada).

Una promesa pendiente puede resolverse (cumplirse) con un valor o rechazarse con un motivo. Una vez saldada, una promesa no se puede volver a saldar.

Creación de Promesas

Veamos la sintaxis de crear una nueva promesa:

new Promise( /* executor */ (resolve, reject) => {});

El constructor de Promise API acepta una función llamada executor . La función ejecutora acepta dos argumentos:resolve y reject , que también son funciones. La función ejecutora es invocada inmediatamente por el creador de Promise, pasando resolve y reject funciones Si las operaciones asincrónicas se completan con éxito, se devuelve el valor esperado llamando al resolve función. Si se produce un error en la función ejecutora, el motivo se transmite llamando al reject función.

Basta de hablar, vamos a crear una promesa simple de setTimeout() y luego utilícelo para registrar el mensaje:

const wait = ms => new Promise((resolve, reject) => setTimeout(resolve, ms));
wait(2000).then(() => console.log('I waited 2 seconds.'));
// I waited 2 seconds.

Una vez que se crea la promesa, podemos agregar controladores de devolución de llamada para cuando se complete la devolución de llamada, usando then() y catch() métodos de la promesa. Ahora vamos a crear otra promesa que se resuelva o rechace aleatoriamente:

const wait = ms => new Promise((resolve, reject) => setTimeout(() => {
    if (Math.random() >= 0.5) {
        resolve('Promise is completed.');
    } else {
        reject('Promise is rejected.')
    }
}, ms));

wait(2000).then(value => console.log(value)).catch(err => console.error(err));

Cadena de promesas

Desde el Promise.prototype.then() El método siempre devuelve una nueva promesa, podemos encadenar varias promesas juntas. Si se encadenan, las promesas se resolverán en una secuencia que se ejecuta sincrónicamente. A través del encadenamiento, también podemos decidir dónde deben manejarse los errores.

Aquí hay un ejemplo de cadena de promesa con múltiples rechazos:

const wait = ms => new Promise((resolve, reject) => setTimeout(resolve, ms));

wait(2000)
    .then(() => new Promise((resolve, reject) => resolve('JavaScript')))
    .then(value => console.log(value))
    .then(() => null)
    .then(e => console.log(e))
    .then(() => { throw new Error('Finish'); })
    .catch((err) => console.error(err))
    .finally(() => console.log('Promise is settled.'));

El finally() se llama al método una vez que la promesa es settled independientemente de que se resuelva o se rechace.

Promesa.todo()

El método Promise.all() es útil para ejecutar múltiples promesas en paralelo y espera hasta que todas estén listas. Toma una matriz de promesas como entrada y devuelve una única promesa, que se resuelve cuando todas las promesas se resuelven o cualquiera de ellas se rechaza.

// sum of two numbers
const sum = (a, b) => new Promise((resolve) => resolve(a + b));

// absolute number
const abs = (num) => new Promise((resolve) => resolve(Math.abs(num)));

// Promise.all
Promise.all([sum(2, 6), abs(-15)]).then(result => console.log(result));
// [8, 15]

Gestión de errores

Cualquier excepción lanzada en la función ejecutora de la promesa causará el Promise.prototype.then() función a invocar, con una razón como argumento. Podemos pasar un controlador de devolución de llamada a este método para manejar el error:

const promise = new Promise((resolve, reject) => {
    throw new Error('Promise is rejected.');
});

promise
    .then(() => console.log('Success!'))
    .catch(err => console.error(err));

Conclusión

JavaScript promete simplificar el anidamiento de las devoluciones de llamada, lo que facilita la escritura de un código que es más fácil de mantener y comprender. Proporcionan una forma clara y consistente de manejar las devoluciones de llamada. Se pueden encadenar varias promesas para consumir los resultados de una promesa por otra.

Si desea obtener más información, consulte la guía sobre async/await, que es el estándar más reciente (introducido en ES8) para escribir funciones asíncronas en JavaScript.