L'oggetto Promise rappresenta il completamento (o il fallimento) futuro di un'operazione asincrona e il valore che ne risulta.
Questo è un argomento difficile per molte persone, soprattutto se sai programmare in un linguaggio completamente sincrono. Se ti senti sopraffatto, o vorresti saperne di più su concorrenza e parallelismo, guarda (su go.dev) o guarda direttamente su vimeo e leggi le slide del brillante intervento «Concurrency is not parallelism».
Una Promise ha tre stati:
Quando viene creata, una promise è nello stato pending. A un certo punto nel futuro potrà resolve o reject. Una volta che una promise raggiunge lo stato resolved o rejected, non potrà mai più diventare resolved o rejected, né il suo stato può cambiare.
In altre parole:
Una promise può essere risolta in vari modi:
// 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;
});
Negli esempi qui sopra value può essere qualsiasi cosa, incluso un errore, undefined, null o un'altra promise.
Di solito vorrai risolvere con un valore che non sia un errore.
Una promise può essere rifiutata in vari modi:
// 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
})
Negli esempi qui sopra reason può essere qualsiasi cosa, incluso un errore, undefined o null.
Di solito vorrai rifiutare con un errore.
A una promise si può collegare un'azione futura, eseguita quando si risolve o si rifiuta.
promise.then() viene chiamata quando promise si risolvepromise.catch() viene chiamata quando promise si rifiutapromise.finally() viene chiamata quando promise si risolve o si rifiutaOgni promise è «thenable».
Questo significa che è disponibile una funzione then che verrà eseguita una volta che la promise originale è risolta.
Dato promise.then(onResolved), la callback onResolved riceve il valore con cui è stata risolta la promise originale.
Questa restituirà sempre una nuova promise «concatenata».
Restituire un value da then risolve la promise «concatenata».
Lanciare un reason in then rifiuta la promise «concatenata».
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;
});
Questo registrerà "Success!" dopo circa 1000 ms.
Lo stato e il valore di promise1 saranno resolved e "Success!".
Lo stato e il valore di promise2 saranno resolved e true.
È disponibile un secondo argomento che viene eseguito quando la promise originale viene rifiutata.
Dato promise.then(onResolved, onRejected), la callback onResolved riceve il valore con cui è stata risolta la promise originale, oppure la callback onRejected riceve il motivo per cui la promise è stata rifiutata.
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!" dopo circa 1000 ms.
promise1 saranno resolved e "Success!".promise2 saranno resolved e true."NOPE!".
promise1 saranno rejected e Nope!.promise2 saranno resolved e false.È importante capire che, a causa delle regole del ciclo di vita, quando la promise esegue reject, la resolve che arriva circa 1000 ms dopo viene ignorata silenziosamente: lo stato interno non può cambiare una volta che è diventato rejected o resolved.
È importante capire che restituire un valore da una promise la risolve, e lanciare un valore la rifiuta.
Quando promise1 si risolve e c'è un onResolved concatenato: then(onResolved), quella continuazione è una nuova promise che può risolversi o rifiutarsi.
Quando promise1 viene rifiutata ma c'è un onRejected concatenato: then(, onRejected), quella continuazione è una nuova promise che può risolversi o rifiutarsi.
A volte vuoi catturare gli errori e continuare solo quando la promise originale esegue reject.
Dato promise.catch(onCatch), la callback onCatch riceve il motivo per cui la promise originale è stata rifiutata.
Questa restituirà sempre una nuova promise «concatenata».
Restituire un value da catch risolve la promise «concatenata».
Lanciare un reason in catch rifiuta la promise «concatenata».
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);
In circa 1/2 dei casi, registrerà "Success!" dopo circa 1000 ms.
Nell'altro 1/2 dei casi, registrerà immediatamente 42.
promise1 si risolve, catch viene saltata, si arriva a then e viene registrato il valore.
promise1 saranno resolved e "Success!".promise2 saranno resolved e "done";promise1 viene rifiutata, catch viene eseguita, il che restituisce un valore: la catena ora è resolved, si arriva a then e viene registrato il valore.
promise1 saranno rejected e "Nope!".promise2 saranno resolved e "done";A volte vuoi eseguire del codice dopo che una promise si è conclusa, indipendentemente dal fatto che si risolva o venga rifiutata.
Dato promise.finally(onSettled), la callback onSettled non riceve nulla.
Questa restituirà sempre una nuova promise «concatenata».
Restituire un value da finally copia lo stato e il valore dalla promise originale, ignorando il value.
Lanciare un reason in finally rifiuta la promise «concatenata», sovrascrivendo qualsiasi stato, valore o motivo della promise originale.
Diversi metodi usati insieme:
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 è 0-3:
myPromise sarà risolta con il valore 2, 4, 6, or 8
finalPromise sarà risolta con il valore 'yay'
Sampled data: ...Promise completedrandomNumber è 4-5:
myPromise sarà rifiutata con il motivo 'Sampling did not result in a sample'
finalPromise sarà rifiutata con il motivo Error('Sampling did not result in a sample')
Promise completed"uncaught rejected promise: Error('Sampling did not result in a sample')"
Come mostrato sopra, reject funziona con una stringa, e una promise può anche essere rifiutata con un Error.
Se concatenare le promise o il loro uso generale non ti è chiaro, il tutorial su MDN è una buona risorsa da consultare.
In questo esercizio fornirai un TranslationService che offre servizi di traduzione di base agli utenti gratuiti e traduzioni avanzate agli utenti premium con garanzie di qualità.
L'API
Hai trovato un'API di traduzione spaziale che soddisfa qualsiasi request di traduzione in un tempo ragionevole.
Vuoi trarne vantaggio.
I traduttori spaziali sono estremamente capricciosi e odiano le ripetizioni, quindi forniscono anche dei satelliti di archivio API dove puoi recuperare con fetch le traduzioni passate senza disturbarli.
Recuperare una traduzione
api.fetch(text) recupera una traduzione di text dall'archivio API e restituisce una promise che fornisce due valori:
translation: la traduzione effettivaquality: la qualità espressa come numeroSe una traduzione non viene trovata nell'archivio API, l'API lancia un errore NotAvailable.
Le traduzioni possono essere aggiunte usando il metodo api.request.
Se 'text' non è traducibile, l'API lancia un errore Untranslatable.
api.fetch('jIyaj');
// => Promise({ resolved: 'I understand' })
Richiedere una traduzione
Alcune traduzioni esistono sicuramente, ma non sono ancora state aggiunte all'archivio API. Questa è la differenza tra NotAvailable (non presente nell'archivio, ma richiedibile) e Untranslatable (non traducibile).
api.request(text, callback) richiede che venga eseguita una traduzione di text e che venga aggiunta all'archivio API.
Al termine viene chiamata la funzione callback.
callback viene passato undefined: questo indica che la traduzione è andata a buon fine ed è accessibile usando il metodo api.fetch.callback viene passato un error: questo indica che qualcosa è andato storto.
L'API spaziale è instabile, il che significa che fallisce spesso.
Se succede, va bene richiamare api.request.api.request('majQa’', callback);
// => undefined
//
// later: the passed callback is called with undefined
// because it was successful.
⚠ Attenzione! ⚠
L'API fa la sua magia teletrasportando i vari traduttori quando arriva una request.
È un'azione molto costosa, quindi non va chiamata quando una traduzione è disponibile.
Purtroppo non tutti leggono il manuale, quindi c'è un sistema per espellere chi si comporta male.
Se si chiama api.request per un text che è disponibile, l'API lancia un AbusiveClientError per questa chiamata, e per ogni chiamata successiva.
Assicurati di non richiedere mai una traduzione se qualcosa è già stato tradotto.
Il servizio gratuito fornisce solo le traduzioni attualmente presenti nell'archivio API.
Implementa un metodo free(text) che fornisce agli utenti gratuiti la traduzione già presente nell'archivio API.
Ignora la qualità e inoltra eventuali errori lanciati dall'API.
api.fetch (api.fetch restituisce una promise)service.free('jIyaj');
// => Promise<...> resolves "I understand."
service.free("jIyajbe'");
// => Promise<...> rejects Error("Not yet translated")
Implementa un metodo batch([text, text, ...]) per gli utenti gratuiti che traduce un array di testi usando il servizio gratuito, restituendo tutte le traduzioni, o un singolo errore.
BatchIsEmpty se non viene fornito alcun testoservice.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 metodo request(text) per gli utenti premium, che richiede che una traduzione venga aggiunta all'archivio API.
La richiesta deve riprovare automaticamente se si verifica un errore.
Non deve effettuare più di 3 chiamate per la stessa richiesta (non far arrabbiare i traduttori spaziali!!!).
api.request non restituisce un errore, risolvi con undefined
api.request restituisce un errore, riprova al massimo due volteservice.request("jIyajbe'");
// => Promise<...> resolves (with nothing), can now be retrieved using the fetch API
Implementa un metodo premium(text, quality) per gli utenti premium che recupera una traduzione.
Se una traduzione è NotAvailable, richiedila e recuperala dopo che è stata aggiunta all'archivio API.
Il metodo deve restituire la traduzione solo se soddisfa una certa soglia di quality.
api.fetch si risolve, controlla la qualità prima di risolvereapi.fetch viene rifiutata, richiedi invece la traduzioneapi.request viene rifiutata, inoltra l'erroreservice.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.
The correct translation of 'arlogh Qoylu'pu'? is How many times has it been heard?.
Iscriviti a Exercism per imparare e padroneggiare JavaScript con 37 concetti159 esercizi e il mentoring di persone reali, tutto gratis.