El objeto Promise representa la finalización futura (o el fallo) 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 síncrono. Si te sientes abrumado, 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 ha resuelto o rechazado, nunca podrá volver a resolverse ni rechazarse, ni podrá cambiar de estado.
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.
Normalmente 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.
Normalmente 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 rechazaTodas las promesas son «thenable».
Eso significa que existe 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 registrará "Success!" 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 el motivo por el 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!" después de unos 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 una promesa se rechaza, el resolve que llega unos 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 continuar solo cuando la promesa original se rechaza.
Dado promise.catch(onCatch), el callback onCatch recibe el motivo por el 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 registrará "Success!" después de unos 1000 ms.
En la otra mitad de los casos, esto registrará inmediatamente 42.
promise1 se resuelve, se omite catch, se llega a then y se registra el valor.
promise1 serán resolved y "Success!".promise2 serán resolved y "done";promise1 se rechaza, se ejecuta catch, que devuelve un valor, por lo que la cadena ahora está resolved, se llega a then y se registra 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 se haya resuelto o rechazado, independientemente de 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, valor o motivo 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 el motivo 'Sampling did not result in a sample'
finalPromise se rechazará con el motivo 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.
En este ejercicio, vas a crear un TranslationService que ofrece servicios básicos de traducción a los miembros gratuitos y traducción avanzada a los miembros premium con garantías de calidad.
La API
Has encontrado una API de traducción del espacio exterior que satisface cualquier request de traducción en un tiempo razonable.
Quieres sacarle partido.
Los traductores espaciales son extremadamente caprichosos y odian la redundancia, así que también proporcionan satélites de API storage donde puedes fetch traducciones anteriores sin molestarlos.
Obtener una traducción
api.fetch(text) obtiene una traducción de text del API storage y devuelve una promise que proporciona dos valores:
translation: la traducción en síquality: la calidad expresada como un númeroSi no se encuentra una traducción en el API storage, la API lanza un error NotAvailable.
Las traducciones se pueden añadir con el método api.request.
Si el texto no es traducible, la API lanza un error Untranslatable.
api.fetch('jIyaj');
// => Promise({ resolved: 'I understand' })
Solicitar una traducción
Algunas traducciones existen con seguridad, pero aún no se han añadido al API storage. 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 añada al API storage.
Al terminar, se llama a la función callback.
undefined a callback: esto indica que la traducción se ha realizado correctamente y es accesible con el método api.fetch.error a callback: esto indica que algo ha salido mal.
La API del espacio exterior es inestable, lo que significa que falla a menudo.
Si ocurre eso, no pasa nada por 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 una request.
Es una acción muy costosa, así que no debería llamarse cuando una traducción sí está disponible.
Por desgracia, no todo el mundo se lee el manual, así que hay un sistema para expulsar a los malos actores.
Si se llama a api.request para un text que sí está disponible, la API lanza un AbusiveClientError en esa llamada, y en todas las siguientes.
Asegúrate de no solicitar nunca una traducción si algo ya se ha traducido.
El servicio gratuito solo proporciona traducciones que ya están en el API storage.
Implementa un método free(text) que proporcione a los miembros gratuitos traducciones que ya existen en el API storage.
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 único 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 para usuarios premium, request(text), que solicite que se añada una traducción al API storage.
La solicitud debe reintentarse automáticamente si se produce un fallo.
No debe hacer más de 3 llamadas para la misma solicitud (¡no molestes 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 para usuarios premium, premium(text, quality), para obtener una traducción.
Si una traducción es NotAvailable, solicita la traducción y óbtenla después de que se haya añadido al API storage.
El método solo debe devolver la traducción si cumple un umbral de quality determinado.
api.fetch se resuelve, comprueba 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?"
Nota
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.