Javascript. Promises, todo lo que necesitas saber (I)
Aprende los conceptos básicos sobre el objeto Promise para realizar tareas asíncronas en Javascript
La inclusión del soporte nativo para trabajar con Promises fue una de las inclusiones más importantes que trajo consigo la sexta especificación de Javascript (ECMAScript 2015 / ES6) ya que introducían la posibilidad de trabajar con código asíncrono de una forma mucho más sencilla que los callbacks.
En este artículo quiero realizar un repaso sobre cada uno de los conceptos relacionados con las Promises en Javascript de cara a que nos sintamos cómodos cuando tengamos que trabajar con ellas.
¡Vamos allá!
El objeto Promise
Podemos entender un objeto Promise como la representación de una tarea que en algún momento del futuro proporcionará un resultado que será consumido por alguien.
Podemos crear un objeto Promise
de una forma muy sencilla medinate su constructor:
const myFirstPromise = new Promise(function(resolve, reject) {
// do a task
});
La función que es pasada como argumento al constructor de Promise
se conoce como “ejecutor”. Nada más crearse el objeto, esta función será invocada automáticamente. Además, recibe dos argumentos:
resolve
, una función que podremos invocar con un valor (resolve('foo')
) para indicar a Javascript que la tarea que teníamos que hacer se ha completado sin incidencias.reject
, función que invocaremos en el caso de que haya sucedido algún error durante el transcurso de la tarea. Esta función la invocaremos pasándole el error que queramos (reject(error)
).
Invocando cualquiera de las funciones anteriores lo que haremos será establecer la propiedad result
del objeto promise al valor o error que hayamos indicado.
Estado de una promise
Los objetos promise mantienen un estado interno que puede adquirir 3 valores:
- “pending” mientras la tarea se completa.
- “fullfilled”, si la tarea se completó con éxito (invocando
resolve
) - “rejected” en el caso de que se produjese un error (invocando
reject
)
Por convención, cuando un objeto promise pasa al estado “fullfilled” o “rejected” se emplea el término “settled” para describir que la Promise ha terminado su ejecución.
Creando nuestra primera Promise
Ahora que ya sabemos qué es una Promise
lo siguiente será crear nuestro primer objeto de este tipo y para ello recurriremos a un ejemplo muy didáctico: establecer una cuenta atrás por medio de la función setTimeout
en forma de Promesa. El código es el siguiente:
const myFirstPromise = new Promise(function(resolve, reject) {
setTimeout(() => resolve('finished'), 3000);
});
Tal y como indicábamos antes, nada más construir el nuevo objeto Promise
se invocará la función pasada como argumento de modo que quedará establecido el timeout de 3 segundos. Una vez que pasen esos 3 segundos, invocaremos la función resolve
para marcar que:
- La promise se resolvió con éxito (estado “fullfilled”).
- El resultado ( propiedad interna del objeto Promise almacenada en
result
) de la ejecución será el stringfinished
.
En el caso de que hubiéramos invocado reject
en vez de resolve
:
const myFirstPromise = new Promise(function(resolve, reject) {
setTimeout(() => reject(new Error('failed')), 3000);
});
lo que obtendríamos sería:
- El estado del objeto promise quedaría como “rejected”.
- El resultado sería el error creado con el mensaje “failed”.
❗️ Importante. Si invocamos varias veces la función resolve
( o reject
) dentro de la función que pasamos a la Promise sólo se tendrá en cuenta el primer resultado.
✅ Buenas prácticas. Aunque la función reject
puede recibir cualquier valor, es recomendable pasarle un objeto de tipo Error
.
Consumiendo una Promise
Bien, ahora que ya sabemos crear Promises, ¿cómo accedemos al valor que devuelven? Esto podemos realizarlo por medio de los métodos then
, catch
y finally
. Veamos cada uno de ellos.
Método then
El método then
recibe dos argumentos:
- El primero es una función que recibe como argumento el valor devuelto por la promise en el caso de que se haya resuelto correctamente.
- El segundo es una función que recibe como argumento el error devuelto por la promise en el caso de que se haya invocado
reject
.
Siguiendo con nuestro ejemplo anterior:
const myFirstPromise = new Promise(function(resolve, reject) {
setTimeout(() => resolve('finished'), 3000);
});myFirstPromise.then(
function(result) { console.log(result); },
function(error) { /* do something when rejected */}
);// logs: "finished"
Método catch
Si bien podemos emplear el segundo argumento del método then
para capturar los errores que hayan surgido durante la ejecución de la promesa, lo habitual es emplear el método catch
para ello:
const myFirstPromise = new Promise(function(resolve, reject) {
setTimeout(() => reject(new Error('failed')), 3000);
});
myFirstPromise.catch(
function(error) { console.log(error.message); }
);// logs: "failed"
Método finally
Puede ser que queramos ejecutar alguna pieza de código sea cual sea el resultado final de la promise (“fullfilled” o “rejected”). En este caso podemos emplear el método finally
:
const myFirstPromise = new Promise(function(resolve, reject) {
setTimeout(() => reject(new Error('failed')), 3000);
});
myFirstPromise.finally(
function() { console.log('promise finished'); }
);// logs: "promise finished"
La función que pasamos al método finally
no recibe argumentos (por lo que no podemos acceder al resultado de la promise ni saber si se resolvió correcta o incorrectamente.
Además, invocar el método finally
no captura el resultado de la Promise de modo que su resultado o error pueden ser gestionados por los métodos then
y catch
que aparezcan posteriormente.
const myFirstPromise = new Promise(function(resolve, reject) {
setTimeout(() => resolve('finished'), 3000);
});myFirstPromise
.finally(
function() { console.log('promise finished'); }
)
.then(
function(result) { console.log(result); } // "finished"
);
Promises y el Event Loop
En el siguiente artículo podéis leer más en detalle sobre las Promises y el Event Loop de Javascript (herramienta integrada en el motor que permite la ejecución de tareas asíncronas).
Sin embargo, creo que es importante mencionar en este artículo una casuística concreta para no caer en errores de concepto al trabajar con Promises.
Aunque una promesa la resolvamos inmediatamente sin realizar ninguna tarea asíncrona en su “ejecutor”, sus callbacks ( then
y catch
) siempre serán ejecutados tras haber completado la tarea principal, de modo que el siguiente código imprime los logs en el orden esperado:
const promise = new Promise(resolve => resolve('tercero'));
console.log('primero');
promise.then(result => console.log(result));
console.log('segundo');// primero
// segundo
// tercero
Conclusiones
En este primer artículo he querido repasar los conceptos principales del objeto Promise de Javascript.
Sin embargo, esto es tan sólo la superficie y queda mucho más por aprender. En los siguientes capítulos hablaré de cómo podemos encadenar promesas, gestionar los errores que ocurran dentro de ellas de forma adecuada y de la famosa estructura async / await
que ha simplificado aún más la ejecución de código asíncrono.
¡Te espero en los siguientes capítulos!
¿Quieres recibir más artículos como este?
Si te ha gustado este artículo te animo a que te suscribas a la newsletter que envío cada domingo con publicaciones similares a esta y más contenido recomendado: 👇👇👇
Apóyame en Patreon
🧡🧡🧡 Gracias a: Ktron, Joseba, Alex y Jorge.