L'oggetto Promise rappresenta il completamento (o il fallimento) eventuale di un'operazione asincrona e il valore che ne risulta.
Questo è un argomento difficile per molte persone, soprattutto se conosci la programmazione in un linguaggio completamente sincrono. Se ti senti sopraffatto, o se vuoi 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 è in stato pending. A un certo punto, in futuro, potrà essere risolta o rifiutata. Una volta che una promise è stata risolta o rifiutata, non potrà mai più essere risolta o rifiutata, né il suo stato potrà 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 vuoi 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 vuoi rifiutare con un errore.
Una promise può essere proseguita con un'azione futura una volta che viene risolta o rifiutata.
promise.then() viene chiamata quando promise viene risoltapromise.catch() viene chiamata quando promise viene rifiutatapromise.finally() viene chiamata quando promise viene risolta o rifiutataOgni promise è «thenable».
Questo significa che è disponibile una funzione then che verrà eseguita quando la promise originale viene risolta.
Data promise.then(onResolved), la callback onResolved riceve il valore con cui la promise originale è stata risolta.
Il risultato è 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 stamperà "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.
C'è un secondo argomento disponibile che viene eseguito quando la promise originale viene rifiutata.
Data promise.then(onResolved, onRejected), la callback onResolved riceve il valore con cui la promise originale è stata risolta, 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, per via delle regole del ciclo di vita, quando si chiama reject, la chiamata a resolve che arriva circa 1000 ms dopo viene ignorata silenziosamente, perché lo stato interno non può cambiare una volta che è stato rifiutato o risolto.
È importante capire che restituire un valore da una promise la risolve, e lanciare un valore la rifiuta.
Quando promise1 viene risolta e c'è una onResolved concatenata: then(onResolved), allora quel seguito è una nuova promise che può essere risolta o rifiutata.
Quando promise1 viene rifiutata ma c'è una onRejected concatenata: then(, onRejected), allora quel seguito è una nuova promise che può essere risolta o rifiutata.
A volte vuoi catturare gli errori e continuare solo quando la promise originale viene rifiutata, cioè quando si chiama reject.
Data promise.catch(onCatch), la callback onCatch riceve il motivo per cui la promise originale è stata rifiutata.
Il risultato è 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 metà dei casi, stamperà "Success!" dopo circa 1000 ms.
Nell'altra metà dei casi, stamperà immediatamente 42.
promise1 viene risolta, catch viene saltata e si arriva a then, che stampa il valore.
promise1 saranno resolved e "Success!".promise2 saranno resolved e "done";promise1 viene rifiutata, catch viene eseguita, il che restituisce un valore, e quindi la catena ora è resolved e si arriva a then, che stampa 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 venga risolta o rifiutata.
Data promise.finally(onSettled), la callback onSettled non riceve nulla.
Il risultato è 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 lo stato, il valore o il 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 il concatenamento delle promise o il loro uso generale non ti è chiaro, il tutorial su MDN è una buona risorsa da consultare.