El objeto Promise representa la finalización (o el fallo) eventual de una operación asíncrona y su valor resultante.
Este es un tema difícil para mucha gente, sobre todo si sabes programar en un lenguaje que es completamente sincrónico. Si te sientes abrumado o te gustaría aprender más sobre la concurrencia y el paralelismo, mira (en go.dev) o mira directamente en Vimeo y lee las diapositivas de la brillante charla «Concurrency is not parallelism».
Una Promise tiene tres estados:
Cuando se crea, una promesa está pendiente. En algún momento futuro puede resolverse o rechazarse. Una vez que una promesa se ha resuelto o rechazado, nunca podrá volver a resolverse ni rechazarse, ni su estado podrá cambiar.
En otras palabras:
Una promesa se puede resolver de varias formas:
// Creates a promise that is immediately resolved
Promise.resolve(value);
// Creates a promise that is immediately resolved
new Promise((resolve) => {
resolve(value);
});
// Chaining a promise leads to a resolved promise
somePromise.then(() => {
// ...
return value;
});
En los ejemplos anteriores, value puede ser cualquier cosa, incluido un error, undefined, null u otra promesa.
Normalmente querrás resolver con un valor que no sea un error.
Una promesa se puede rechazar de varias formas:
// Creates a promise that is immediately rejected
Promise.reject(reason)
// Creates a promise that is immediately rejected
new Promise((_, reject) {
reject(reason)
})
// Chaining a promise with an error leads to a rejected promise
somePromise.then(() => {
// ...
throw reason
})
En los ejemplos anteriores, reason puede ser cualquier cosa, incluido un error, undefined o null.
Normalmente querrás rechazar con un error.
Una promesa se puede continuar con una acción futura una vez que se resuelve o se rechaza.
promise.then() se llama una vez que promise se resuelvepromise.catch() se llama una vez que promise se rechazapromise.finally() se llama una vez que promise se resuelve o se rechazaToda promesa es «thenable».
Eso significa que hay una función then disponible que se ejecutará una vez que la promesa original se resuelva.
Dado promise.then(onResolved), la función callback onResolved recibe el valor con el que se resolvió la promesa original.
Esto siempre devolverá una nueva promesa «encadenada».
Devolver un value desde then resuelve la promesa «encadenada».
Lanzar un reason en then rechaza la promesa «encadenada».
const promise1 = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve('Success!');
}, 1000);
});
const promise2 = promise1.then(function (value) {
console.log(value);
// expected output: "Success!"
return true;
});
Esto imprimirá "Success!" tras aproximadamente 1000 ms.
El estado y el valor de promise1 serán resolved y "Success!".
El estado y el valor de promise2 serán resolved y true.
Hay un segundo argumento disponible que se ejecuta cuando la promesa original se rechaza.
Dado promise.then(onResolved, onRejected), la función callback onResolved recibe el valor con el que se resolvió la promesa original, o la función callback onRejected recibe la razón por la que se rechazó la promesa.
const promise1 = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve('Success!');
}, 1000);
if (Math.random() < 0.5) {
reject('Nope!');
}
});
function log(value) {
console.log(value);
return true;
}
function shout(reason) {
console.error(reason.toUpperCase());
return false;
}
const promise2 = promise1.then(log, shout);
"Success!" tras aproximadamente 1000 ms.
promise1 serán resolved y "Success!".promise2 serán resolved y true."NOPE!".
promise1 serán rejected y Nope!.promise2 serán resolved y false.Es importante entender que, debido a las reglas del ciclo de vida, cuando se ejecuta reject, el resolve que llega ~1000 ms después se ignora en silencio, ya que el estado interno no puede cambiar una vez que se ha rechazado o resuelto.
Es importante entender que devolver un valor desde una promesa la resuelve, y lanzar un valor la rechaza.
Cuando promise1 se resuelve y hay un onResolved encadenado: then(onResolved), esa continuación es una nueva promesa que puede resolverse o rechazarse.
Cuando promise1 se rechaza pero hay un onRejected encadenado: then(, onRejected), esa continuación es una nueva promesa que puede resolverse o rechazarse.
A veces quieres capturar errores y solo continuar cuando la promesa original se rechaza.
Dado promise.catch(onCatch), la función callback onCatch recibe la razón por la que se rechazó la promesa original.
Esto siempre devolverá una nueva promesa «encadenada».
Devolver un value desde catch resuelve la promesa «encadenada».
Lanzar un reason en catch rechaza la promesa «encadenada».
const promise1 = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve('Success!');
}, 1000);
if (Math.random() < 0.5) {
reject('Nope!');
}
});
function log(value) {
console.log(value);
return 'done';
}
function recover(reason) {
console.error(reason.toUpperCase());
return 42;
}
const promise2 = promise1.catch(recover).then(log);
En aproximadamente la mitad de los casos, esto imprimirá "Success!" tras aproximadamente 1000 ms.
En la otra mitad de los casos, esto imprimirá inmediatamente 42.
promise1 se resuelve, se omite catch y se llega a then, que imprime el valor.
promise1 serán resolved y "Success!".promise2 serán resolved y "done";promise1 se rechaza, se ejecuta catch, que devuelve un valor, y por tanto la cadena ahora está resolved, y se llega a then, que imprime el valor.
promise1 serán rejected y "Nope!".promise2 serán resolved y "done";A veces quieres ejecutar código después de que una promesa llegue a su estado final, independientemente de si se resuelve o se rechaza.
Dado promise.finally(onSettled), la función callback onSettled no recibe nada.
Esto siempre devolverá una nueva promesa «encadenada».
Devolver un value desde finally copia el estado y el valor de la promesa original, ignorando el value.
Lanzar un reason en finally rechaza la promesa «encadenada», sobrescribiendo cualquier estado y valor o razón de la promesa original.
Varios de los métodos juntos:
const myPromise = new Promise(function (resolve, reject) {
const sampleData = [2, 4, 6, 8];
const randomNumber = Math.round(Math.random() * 5);
if (sampleData[randomNumber]) {
resolve(sampleData[randomNumber]);
} else {
reject('Sampling did not result in a sample');
}
});
const finalPromise = myPromise
.then(function (sampled) {
// If the random number was 0, 1, 2, or 3, this will be
// reached and the number 2, 4, 6, or 8 will be logged.
console.log(`Sampled data: ${sampled}`);
return 'yay';
})
.catch(function (reason) {
// If the random number was 4 or 5, this will be reached and
// reason will be "An error occurred". The entire chain will
// then reject with an Error with the reason as message.
throw new Error(reason);
})
.finally(function () {
// This will always log after either the sampled data is
// logged or the error is raised.
console.log('Promise completed');
});
randomNumber es 0-3:
myPromise se resolverá con el valor 2, 4, 6, or 8
finalPromise se resolverá con el valor 'yay'
Sampled data: ...Promise completedrandomNumber es 4-5:
myPromise se rechazará con la razón 'Sampling did not result in a sample'
finalPromise se rechazará con la razón Error('Sampling did not result in a sample')
Promise completed"uncaught rejected promise: Error('Sampling did not result in a sample')"
Como se muestra arriba, reject funciona con un string, y una promesa también puede rechazarse con un Error.
Si el encadenamiento de promesas o su uso general no te queda claro, el tutorial de MDN es un buen recurso que consultar.