👉 Ver todas las notas
- Intro
- Métodos
- tl;dr Para qué sirven las Promises?
- Estados de una promesa
- Crear promesas
- Promise Chaining
Promise.all
- Error handling
- Promises, microtasks y el Event Loop
- Compatibilidad y soporte
Una promesa en JavaScript es como una promesa en la vida real: nos comprometemos a hacer algo y esta acción tiene 2 resultados posibles, que se cumpla (se resuelve exitosamente) o no (es rechazada).
👉 Mas técnicamente, una promesa es un objeto que representa un valor que puede estar disponible en algún momento del futuro, o nunca. Es una promesa de un valor futuro. Este valor representa a su vez el resultado exitoso o fracaso de ejecutar una tarea asincrónica.
Vamos a utilizar promises para escribir código asincrónico, es decir, tareas que sabemos no van a tener un resultado inmediato, sino que van a tomar cierto tiempo, por ejemplo hacer un request a una API por medio de fetch
, descargar una imagen o realizar una consulta a una base de datos.
👉 La principal ventaja frente a usar callbacks (otra forma que tenemos de manejar asincronismo en JavaScript) es que las promises nos proveen una alternativa para evitar caer en el callback hell, a través de una sintaxis más concisa, limpia y fácil de razonar.
Además, a diferencia de los callbacks, las promesas se pueden componer, utilizando el resultado o output de una como el input de otra.
Al tratarse de un objeto, las promesas tienen una interfaz que nos permite interactuar con estas.
En particular, vamos a estar estar usando los metodos .then()
y .catch()
👉 Estos métodos nos van a permitir operar con un eventual valor en caso de éxito (el .then()
), o de falla (el ´catch()´). Esto permite que métodos asincrónicos devuelvan valores como si fueran sincrónicos: en vez de inmediatamente retornar el valor final, el método asincrónico devuelve una promesa de darnos el valor en algún momento en el futuro.
then()
: el código dentro se va a ejecutar sólo si la promesa se resuelva de forma exitosacatch()
: el código dentro se va a ejecutar en caso de error (la promesa es rechazada)all()
: permite ejecutar varias promesas de forma concurrente
Los métodos then()
y catch()
devuelven a su vez promesas, que pueden ser encadenadas
promise
.then(...)
.then(...)
.then(...)
.catch(...)
Nota:
- nos permiten escribir código asincrónico de forma más legible, evitando el Callback Hell
- nos permiten un mejor manejo de los errores
- las promesas se pueden componer, utilizando el resultado o output de una como el input de otra.
Una Promesa se encuentra siempre en uno de los siguientes estados:
- pendiente (pending): estado inicial de cualquier promesa, no cumplida o rechazada aún
- resuelta (fulfilled): significa que la operación se completó exitosamente
- rechazada (rejected): significa que la operación falló
Cuando creamos una Promise (objeto), le tenemos que pasar una función callback como argumento. Dentro de este callback, tenemos 2 parámetros: resolve()
y reject()
.
resolve()
: cuando el estado de la promesa pasa a estar resuelto (fullfiled
), se ejecuta el métodoresolve()
. Podemos pasar argumentos que serán llevados al callback del.then()
en el métodoresolve()
reject()
: es el método que ejecutamos si consideramos que la promesa falló (o que debe ser rechazada). Podemos pasar cualquier mensaje de error como argumento, el cual será tomado en el callback del métodocatch()
(que se ejecuta sólo si la promesa falla)
👉 Entonces, cualquier valor que le pasemos al resolve
va a poder ser accedido en el then
cuando estemos usando la promesa (y si esta se resuelve exitosamente) y cualquier valor que le pasemos al reject
va a poder ser accedido en el catch
, en el caso de que falle. Por ejemplo
const promise = new Promise((resolve, reject) => {
const a = 2;
if (a === 2) {
resolve('success!');
} else {
reject('FAIL.');
}
});
promise
.then(res => console.log(res)) // acá tenemos acceso al valor que retorna el `resolve`, loguea 'success!'
.catch(err => console.error(err)); // acá tenemos acceso al valor que retorna el `reject`, loguea 'FAIL.'
Una promesa también puede devolver otra promesa (después de todo, son objetos), que también encadenaremos usando then()
y se ejecutará sólo cuando la promesa anterior esté resuelta. Por lo tanto, las promesas se pueden componer, es decir, usar los resultados de unas como input de otras.
Si la promesa falla y tenemos un método catch()
, se ejecutará para cualquier promesa que tengamos en la cadena.
👉 Este método recibe un array de promises, las ejecuta y retorna una (nueva) promesa a un array con los resultados, si y sólo si todas las promesas se resuelven de forma exitosa.
Permite ejecutar varias promesas de forma concurrente.
Si p1
, p2
, y p3
son funciones que retornan promesas, entonces podríamos hacer
Promise.all([p1(), p2(), p3()])
.then(([res1, res2, res3]) => {
console.log(res1);
console.log(res2);
console.log(res3);
})
donde res1
, res2
y res3
se corresponden con los valores de las promesas resueltas.
👉 Como la respuesta es una promesa a un array, también podemos simplificar el código usando métodos de array, como
forEach
Promise.all([p1(), p2(), p3()])
.then(results => results.forEach(res => console.log(res)));
👉 Vamos a utilizar
Promise.all()
cuando nos interese esperar a tener todas las promesas resueltas, independientemente del orden en que esto suceda (podrían ser promesas que no tengan relación entre si), por eso resulta especialmente útil si estamos utilizandoasync/await
, para de esta forma evitar esperas innecesarias en la ejecución
A través del método catch()
, podemos centralizar el manejo de errores, resultando mucho más simple de mantener que utilizando, por ejemplo callbacks, ya que cualquier promesa que falle (sea rechazada) en una cadena de operaciones, va a terminar siendo manejada en un catch()
al final y ya no necesitamos manejar los errores en cada operación asincrónica de forma separada.
Ejemplo usando Fetch API
fetch('https://pokeapi.co/api/v2/pokemon/ditto/')
.then(x => console.log('success! 😸'))
.catch(x => console.log('fail. 😿'));
Resolver una Promise cuenta como una micro tarea (micro tasks), por lo que van a ejecutarse al inicio de la próxima iteración del Event Loop, teniendo prioridad sobre otras tareas (macrotasks).
Para más detalles, ver Notas sobre el Event Loop
Esta feature de ES6 tiene soporte en todos los browsers modernos, pero no en IE.
Para navegadores sin soporte, podemos usar polyfills.