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 muchas personas, sobre todo si sabes programar en un lenguaje que es completamente síncrono. Si te resulta abrumador, o quieres 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 resuelve o se rechaza, no puede volver a resolverse ni rechazarse, y su estado tampoco puede cambiar.
En otras palabras:
Una promesa puede resolverse 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.
Por lo general, querrás resolver con un valor que no sea un error.
Una promesa puede rechazarse 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.
Por lo general, querrás rechazar con un error.
Una promesa puede continuarse con una acción futura una vez que se resuelve o se rechaza.
promise.then() una vez que promise se resuelvepromise.catch() una vez que promise se rechazapromise.finally() 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), el 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!" en la consola después de 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), el callback onResolved recibe el valor con el que se resolvió la promesa original, o el 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!" en la consola después de aproximadamente 1000 ms.
promise1 serán resolved y "Success!".promise2 serán resolved y true."NOPE!" en la consola.
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 llama a reject, el resolve que llega unos 1000 ms más tarde 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), el 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!" en la consola después de aproximadamente 1000 ms.
En la otra mitad de los casos, esto imprimirá inmediatamente 42 en la consola.
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 lo 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 finalice, sin importar si se resuelve o se rechaza.
Dado promise.finally(onSettled), el 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 encadenar promesas o el uso general no te queda claro, el tutorial de MDN es un buen recurso para consultar.
En este ejercicio, vas a implementar un TranslationService que brinda servicios básicos de traducción a los miembros gratuitos, y traducción avanzada, con garantía de calidad, a los miembros premium.
La API
Encontraste una API de traducción del espacio exterior que atiende cualquier request de traducción en un tiempo razonable.
Quieres sacarle provecho.
Los traductores espaciales son extremadamente caprichosos y odian la redundancia, así que también ofrecen satélites de almacenamiento de la API donde puedes hacer fetch de traducciones anteriores sin molestarlos.
Obtener una traducción
api.fetch(text) obtiene una traducción de text del almacenamiento de la API y devuelve una promise que entrega dos valores:
translation: la traducción en síquality: la calidad expresada como un númeroSi no se encuentra una traducción en el almacenamiento de la API, la API lanza un error NotAvailable.
Las traducciones se pueden agregar con el método api.request.
Si «text» no se puede traducir, la API lanza un error Untranslatable.
api.fetch('jIyaj');
// => Promise({ resolved: 'I understand' })
Solicitar una traducción
Algunas traducciones existen con seguridad, pero todavía no se han agregado al almacenamiento de la API. Esa es la diferencia entre NotAvailable (no está en el almacenamiento, pero se puede solicitar) y Untranslatable (no se puede traducir).
api.request(text, callback) solicita que se realice una traducción de text y se agregue al almacenamiento de la API.
Al terminar, se llama a la función callback.
undefined a callback: esto indica que la traducción se realizó correctamente y se puede acceder a ella con el método api.fetch.error a callback: esto indica que algo salió mal.
La API del espacio exterior es inestable, lo que significa que la API falla a menudo.
Si eso pasa, está bien volver a llamar a api.request.api.request('majQa’', callback);
// => undefined
//
// later: the passed callback is called with undefined
// because it was successful.
⚠ ¡Advertencia! ⚠
La API hace su magia teletransportando a los distintos traductores cuando llega un request.
Esta es una acción muy costosa, así que no debería llamarse cuando una traducción sí está disponible.
Lamentablemente, no todos leen el manual, así que hay un sistema para expulsar a los que abusan del servicio.
Si se llama a api.request para un text que está disponible, la API lanza un AbusiveClientError para esa llamada, y para todas las llamadas posteriores.
Asegúrate de nunca solicitar una traducción si algo ya se ha traducido.
El servicio gratuito solo ofrece traducciones que ya están en el almacenamiento de la API.
Implementa un método free(text) que entregue a los miembros gratuitos traducciones que ya existen en el almacenamiento de la API.
Ignora la calidad y propaga cualquier error que lance la API.
api.fetch (api.fetch devuelve una promise)service.free('jIyaj');
// => Promise<...> resolves "I understand."
service.free("jIyajbe'");
// => Promise<...> rejects Error("Not yet translated")
Implementa un método batch([text, text, ...]) para los miembros gratuitos que traduzca un array de textos con el servicio gratuito y devuelva todas las traducciones, o un solo error.
BatchIsEmpty si no se proporciona ningún textoservice.batch(['jIyaj', "majQa'"]);
// => Promise<...> resolves ["I understand.", "Well done!"]
service.batch(['jIyaj', "jIyajbe'"]);
// => Promise<...> rejects new Error("Not yet translated")
service.batch([]);
// => Promise<...> rejects BatchIsEmpty()
Implementa un método request(text) para usuarios premium, que solicita que se agregue una traducción al almacenamiento de la API.
La solicitud debe reintentarse automáticamente si ocurre un fallo.
No debe hacer más de 3 llamadas para la misma solicitud (¡no hagas enojar a los traductores espaciales!).
api.request no devuelve un error, resuelve con undefined
api.request devuelve un error, reintenta como máximo dos vecesservice.request("jIyajbe'");
// => Promise<...> resolves (with nothing), can now be retrieved using the fetch API
Implementa un método premium(text, quality) para usuarios premium que obtenga una traducción.
Si una traducción está NotAvailable, solicita la traducción y óbtenla después de que se haya agregado al almacenamiento de la API.
El método solo debe devolver la traducción si cumple con un umbral de quality determinado.
api.fetch se resuelve, verifica la calidad antes de resolverapi.fetch se rechaza, solicita la traducción en su lugarapi.request se rechaza, propaga el errorservice.premium("jIyajbe'", 100);
// => Promise<...> resolves "I don't understand."
service.premium("'arlogh Qoylu'pu'?", 100);
// => Promise<...> rejects QualityThresholdNotMet()
service.premium("'arlogh Qoylu'pu'?", 40);
// => Promise<...> resolves "What time is it?"
N.B.
La traducción correcta de 'arlogh Qoylu'pu'? es How many times has it been heard?.
Regístrate en Exercism para aprender y dominar JavaScript con 37 conceptos159 ejercicios y mentoría humana real, todo gratis.