Articolo originale: JavaScript Promise Tutorial – How to Resolve or Reject Promises in JS
Le Promise sono un concetto molto importante per le operazioni asincrone in JavaScript. Forse pensi che le promise non siano così facili da capire, apprendere e usare. E credimi non sei il solo!
Le Promise sono impegnative per molti sviluppatori web, anche dopo anni passati a usarle.
In questo articolo, cercherò di cambiare questa percezione e condividere quello che ho imparato sulle Promise in JavaScript negli ultimi anni. Spero che ti sia utile.
Cos'è una Promise in JavaScript?
Una Promise è un'oggetto speciale in JavaScript. Produce un valore quando un'operazione asincrona viene completata con successo, o un errore se non è completata con successo a causa di un timeout, un errore di rete, e via dicendo.
Una chiamata riuscita è indicata dalla chiamata della funzione resolve
, mentre gli errori sono indicati dalla chiamata della funzione reject
.
Puoi creare una promise usando il costruttore Promise in questo modo:
let promise = new Promise(function(resolve, reject) {
// Esegui delle operazioni asincrone, quindi chiama resolve o reject
});
Nella maggior parte dei casi, una promise si usa per un'operazione asincrona. Tuttavia, tecnicamente, puoi risolvere/rifiutare sia operazioni sincrone che asincrone.
Aspetta un momento, non abbiamo già le funzioni callback per le operazioni asincrone?
Esatto! In JavaScript esistono le funzioni callback. Ma, una callback non è niente di speciale. È una normale funzione che produce un risultato al completamento di un'operazione asincrona.
La parola 'asincrona' significa che una cosa succede nel futuro, non adesso. Normalmente, le callback si usano solamente per operazioni di rete, o caricare/scaricare file, consultare una base di dati, o cose del genere.
Nonostante le callback siano utili, hanno anche un grande svantaggio. In alcuni casi, possiamo avere una callback dentro un'altra callback dentro un'altra callback, e così via. Dico davvero! Cerchiamo di capire questo "inferno di callback" con un esempio.
Come evitare l'Inferno di callback – Esempio con PizzaHub
Ordiniamo una pizza Margherita Veg🍕 da PizzaHub. Quando facciamo un ordine, PizzaHub individua automaticamente la nostra posizione geografica, trova una pizzeria nei dintorni e controlla se la pizza che vogliamo è disponibile.
Se è disponibile individua che tipo di bibita possiamo avere gratis con la pizza, e finalmente invia l'ordine.
Se l'ordine va a buon fine, riceviamo un messaggio di conferma.
Quindi, come possiamo scrivere un codice per queste operazioni usando le funzioni callback? Io ho pensato a qualcosa del genere:
function orderPizza(type, name) {
// Chiedi a pizzahub di trovare un ristorante
query(`/api/pizzahub/`, function(result, error){
if (!error) {
let shopId = result.shopId;
// Contatta il ristorante e chiedi le pizze
query(`/api/pizzahub/pizza/${shopid}`, function(result, error){
if (!error) {
let pizzas = result.pizzas;
// Controlla se la pizza è dsponibile
let myPizza = pizzas.find((pizza) => {
return (pizza.type===type && pizza.name===name);
});
// Controlla le bibite gratis
query(`/api/pizzahub/beverages/${myPizza.id}`, function(result, error){
if (!error) {
let beverage = result.id;
// Prepara l'ordine
query(`/api/order`, {'type': type, 'name': name, 'beverage': beverage}, function(result, error){
if (!error) {
console.log(`Your order of ${type} ${name} with ${beverage} has been placed`);
} else {
console.log(`Bad luck, No Pizza for you today!`);
}
});
}
})
}
});
}
});
}
// Chiama il metodo orderPizza
orderPizza('veg', 'margherita');
Esaminiamo meglio la funzione orderPizza
.
Chiama una API per ottenere l'id del ristorante più vicino. Dopodiché, riceve la lista delle pizze disponibili nel ristorante. Controlla che la pizza che vogliamo è fra le disponibili e fa un'altra richiesta all'API per cercare le bibite da abbinare alla pizza. E infine invia l'ordine.
Qui stiamo usando una callback per ogni richiesta inviata all'API. Questo ci porta a dover usare una callback dentro un'altra callback, dentro un'altra callback, e così via.
Andiamo verso quello che chiamiamo (molto pittorescamente) inferno di callback. E penso che non piaccia a nessuno. Inoltre forma una piramide di codice che è fonte di confusione ma anche di possibili errori.

Ci sono vari modi per uscire dall'inferno di callback (o per non entrare). Il modo più comune è appunto quello di usare una Promise
o una funzione async
. Tuttavia, per capire bene cosa sia una funzione async
, hai bisogno prima di avere una conoscenza sufficiente delle promise.
Quindi iniziamo e tuffiamoci nelle promise.
Comprendere gli stati delle Promise
Riassumendo, una promise può essere creata con la sintassi del costruttore (constructor), in questo modo:
let promise = new Promise(function(resolve, reject) {
// Codice da eseguire
});
Il costruttore ha come argomento una funzione. Quest'ultima funzione si chiama funzione esecutrice (executor).
// Funzione esecutrice passata come argomento
// al costruttore della promise
function(resolve, reject) {
// la logica va qui...
}
La funzione esecutrice accetta due argomenti, resolve
e reject
. Queste sono le callback fornite dal linguaggio JavaScript stesso. La logica del tuo programma va inserita nel corpo della funzione esecutrice che viene eseguita automaticamente quando una nuova promise è creata con la parola chiave new
.
Per essere effettiva, la funzione executor della promise deve chiamare una delle due callback, resolve
o reject
. Entreremo nei dettagli più avanti.
La funzione constructor new Promise()
restituisce un oggetto promise
. Siccome la funzione executor gestisce operazioni asincrone, l'oggetto promise
deve avere la capacità di informare quando l'esecuzione ha inizio, quando viene completata (resolved) o quando restituisce un errore (rejected).
Un oggetto promise
possiede le seguenti proprietà interne:
state
– Questa proprietà può avere i seguenti valori:
pending
: il valore iniziale quando inizia l'esecuzione della funzione esecutrice (in sospeso).fulfilled
: quando la promise è risolta.rejected
: quando la promise è rifiutata.

2. result
– Questa proprietà può avere i seguenti valori:
undefined
: il valore iniziale quando la proprietàstate
ha valorepending
.valore
: quando viene chiamataresolve(valore)
.errore
: quando viene chiamatareject(errore)
.
Queste proprietà interne, sono inaccessibili da un codice esterno, ma sono ispezionabili. Ovvero possiamo ispezionare il valore delle proprietà state
e result
con gli strumenti di debug, ma non possiamo accedervi direttamente tramite il programma.

Lo stato di una promise può essere pending
(sospesa), fulfilled
(soddisfatta) o rejected
(respinta, rifiutata). Una promise risolta o rifiutata viene chiamata settled
(conclusa).

Come si risolve o si rifiuta una promise
Ecco un esempio di una promise risolta (stato fulfilled
) immediatamente con il valore I am done
.
let promise = new Promise(function(resolve, reject) {
resolve("I am done");
});
La seguente promise è rifiutata (stato rejected
) con il messaggio di errore Something is not right!
.
let promise = new Promise(function(resolve, reject) {
reject(new Error('Something is not right!'));
});
Una cosa importante da notare:
La funzione executor di una Promise deve chiamare solo una voltaresolve
oreject
. Una volta che lo stato passa da pending a fulfilled o da pending a rejected, è tutto. Qualsiasi ulteriore chiamata diresolve
oreject
viene ignorata.
let promise = new Promise(function(resolve, reject) {
resolve("I am surely going to get resolved!");
reject(new Error('Will this be ignored?')); // ignorato
resolve("Ignored?"); // ignorato
});
Nell'esempio precedente, solo la prima chiamata di resolve avviene, mentre il resto viene ignorato.
Come gestire una Promise una volta creata
Una Promise
utilizza una funzione executor per completare un'operazione (di solito asincrona). Una funzione che usa il risultato di una promise, dovrebbe essere avvertita quando la funzione executor ha terminato il suo lavoro chiamando resolve o reject.
I metodi .then()
, .catch()
e .finally()
, servono per creare questo collegamento fra la funzione executor e la funzione che consuma la promise (consumer), in modo tale che possano essere sincronizzate quando la promise è risolta o rifiutata.

Come usare il metodo .then()
Il metodo .then()
dovrebbe essere chiamato sull'oggetto promise per gestire un risultato (resolve) o un errore (reject).
Il metodo accetta due funzioni come parametri. Normalmente, il metodo .then()
viene chiamato dalla funzione consumer quando vuoi conoscere il risultato dell'esecuzione di una promise.
promise.then(
(result) => {
console.log(result);
},
(error) => {
console.log(error);
}
);
Se ti interessa solo il risultato in caso di successo, puoi passare un solo argomento, in questo modo:
promise.then(
(result) => {
console.log(result);
}
);
Se ti interessa solo il risultato in caso di errore, puoi passare null
come primo argomento, in questo modo:
promise.then(
null,
(error) => {
console.log(error)
}
);
In ogni caso, puoi gestire meglio gli errori usando il metodo .catch()
che vedremo in un minuto.
Vediamo un paio di esempi di come gestire i risultati e gli errori usando i metodi .then
e .catch
. Vediamo di rendere le cose un po' più divertenti con delle richieste asincrone reali. Useremo la PokeAPI per richiedere informazioni sui Pokémon e gestire i risultati con le promise e le funzioni resolve/reject.
Per prima cosa, creiamo una funzione generica che accetta un URL della PokeAPI come argomento e ritorna una promise. Se la richiesta alla API ha successo, restituiremo una promise risolta (resolved). In caso di errore, restituiremo una promise rifiutata (rejected).
D'ora in poi useremo questa funzione in vari esempi per ottenere una promise e lavorare con essa.
function getPromise(URL) {
let promise = new Promise(function (resolve, reject) {
let req = new XMLHttpRequest();
req.open("GET", URL);
req.onload = function () {
if (req.status == 200) {
resolve(req.response);
} else {
reject("There is an Error!");
}
};
req.send();
});
return promise;
}
Esempio 1: ottieni informazioni su 50 Pokémon:
const ALL_POKEMONS_URL = 'https://pokeapi.co/api/v2/pokemon?limit=50';
// Abbiamo già parlato di questa funzione!
let promise = getPromise(ALL_POKEMONS_URL);
const consumer = () => {
promise.then(
(result) => {
console.log({result}); // Registra il risultato di 50 Pokemons
},
(error) => {
// Visto che l'URL è valido, questa funzione non sarà chiamata.
console.log('We have encountered an Error!'); // Registra un errore
});
}
consumer();
Esempio 2: proviamo con URL non valido
const POKEMONS_BAD_URL = 'https://pokeapi.co/api/v2/pokemon-bad/';
// Questa promise sarà rifiutata perché l'URL è 404
let promise = getPromise(POKEMONS_BAD_URL);
const consumer = () => {
promise.then(
(result) => {
// La promise non è risolta. Quindi, questa funzione
// non sarà eseguita.
console.log({result});
},
(error) => {
// Una promise rifiutata eseguirà questa funzione
console.log('We have encountered an Error!'); // Registra un errore
}
);
}
consumer();
Come usare il metodo .catch()
Puoi usare questo metodo per gestire gli errori in una promise. La sintassi di passare null
come primo argomento di .then()
non è esattamente un gran modo di gestire gli errori. Per questo abbiamo .catch()
per eseguire lo stesso lavoro con una sintassi più pulita:
// Questa promise sarà rifiutata perché l'URL è 404
let promise = getPromise(POKEMONS_BAD_URL);
const consumer = () => {
promise.catch(error => console.log(error));
}
consumer();
Se lanciamo un errore come new Error("Something wrong!")
invece di chiamare reject
dalla funzione executor, sarà considerato comunque un rifiuto. Ovvero l'errore sarà intercettato dal metodo .catch
.
Lo stesso succede per ogni eccezione sincrona che avviene nella funzione executor o in una funzione di gestione.
Questo è un esempio in cui un errore viene trattato come un rifiuto e il metodo .catch
viene chiamato:
new Promise((resolve, reject) => {
throw new Error("Something is wrong!");// nessuna chiamata di reject
}).catch((error) => console.log(error));
Come usare il metodo .finally()
Il metodo .finally()
svolge operazioni di pulizia come interrompere un caricamento, chiudere una connessione attiva, e così via. Il metodo finally()
viene chiamato a prescindere del fatto che una promise sia risolta o rifiutata. Passa il risultato o l'errore al gestore seguente, che a sua volta potrà chiamare .then()
o .catch()
.
Questo è un esempio che ti aiuterà a capire i tre metodi insieme:
let loading = true;
loading && console.log('Loading...');
// Ottieni la promise
promise = getPromise(ALL_POKEMONS_URL);
promise.finally(() => {
loading = false;
console.log(`Promise Settled and loading is ${loading}`);
}).then((result) => {
console.log({result});
}).catch((error) => {
console.log(error)
});
Per spiegare meglio:
- Il metodo
.finally()
cambia il valore diloading
infalse
. - Se la promise è risolta, il metodo
.then()
viene chiamato. Se la promise è rifiutata con un errore, viene chiamato il metodo.catch()
. Il metodo.finally()
sarà chiamato comunque a prescindere che la promise sia risolta o rifiutata.
Cos'è la catena di Promise?
La chiamata promise.then()
restituisce sempre una promise. Questa promise avrà la proprietà state
con valore pending
e result
sarà undefined
. Ci permette chiamare il metodo .then
sulla nuova promise.
Quando il primo .then
restituisce un valore, il seguente .then
può ricevere questo valore. Il secondo .then
può passarlo al terzo e così via. Tutto questo forma una catena di .then
che trasmette le promise. Questo fenomeno viene chiamato promise chain (catena di promise).

Ecco un esempio:
let promise = getPromise(ALL_POKEMONS_URL);
promise.then(result => {
let onePokemon = JSON.parse(result).results[0].url;
return onePokemon;
}).then(onePokemonURL => {
console.log(onePokemonURL);
}).catch(error => {
console.log('In the catch', error);
});
Qui per prima cosa otteniamo una promise risolta e quindi estraiamo l'URL per raggiungere il primo Pokémon. Quindi restituiamo questo valore che viene passato come promise al seguente metodo .then(). E dunque il risultato:
https://pokeapi.co/api/v2/pokemon/1/
Il metodo .then
può restituire:
- Un valore (già abbiamo visto questo caso)
- Oppure una nuova promise.
Inoltre può anche dare un errore.
Ecco un esempio in cui creiamo una catena di promise con vari metodi .then
che restituiscono dei risultati e una nuova promise:
// Catena di promise con then e catch multipli
let promise = getPromise(ALL_POKEMONS_URL);
promise.then(result => {
let onePokemon = JSON.parse(result).results[0].url;
return onePokemon;
}).then(onePokemonURL => {
console.log(onePokemonURL);
return getPromise(onePokemonURL);
}).then(pokemon => {
console.log(JSON.parse(pokemon));
}).catch(error => {
console.log('In the catch', error);
});
Nel primo .then
estraiamo l'URL e restituiamo il suo valore. Questo URL viene passato al secondo .then
che restituisce una nuova promise che prende l'URL come argomento.
Quest'ultima promise viene risolta e trasmessa nella catena dove otteniamo le informazioni sul Pokémon. Questo è il risultato:

Nel caso in cui ci fosse un errore o una promise rifiutata, il metodo .catch nella catena verrebbe chiamato.
Un punto importante: chiamare .then
più volte non forma una catena di promise. Potresti finire per introdurre un bug nel codice:
let promise = getPromise(ALL_POKEMONS_URL);
promise.then(result => {
let onePokemon = JSON.parse(result).results[0].url;
return onePokemon;
});
promise.then(onePokemonURL => {
console.log(onePokemonURL);
return getPromise(onePokemonURL);
});
promise.then(pokemon => {
console.log(JSON.parse(pokemon));
});
Abbiamo chiamato il metodo .then
tre volte sulla stessa promise, ma non abbiamo trasmesso la promise. Questo è diverso dalla catena di promise. Nell'ultimo esempio, Il risultato sarebbe un errore.

Come gestire promise multiple
Oltre ai gestori (.then, .catch e .finally), esistono sei metodi statici disponibili nella API Promise. I primi quattro metodi accettano un array di promise e le eseguono in parallelo.
- Promise.all
- Promise.any
- Promise.allSettled
- Promise.race
- Promise.resolve
- Promise.reject
Vediamoli uno a uno.
Il metodo Promise.all()
Promise.all([promise])
accetta una collezione (per esempio, un array) di promise come argomento e le esegue in parallelo.
Questo metodo aspetta fino a che tutte le promise siano risolte e ritorna un array con i loro risultati. Se una qualsiasi di queste promise viene rifiutata o non viene eseguita a causa di un errore, i risultati di tutte le altre vengono ignorati.
Creiamo tre promise per ottenere informazioni su tre Pokémon.
const BULBASAUR_POKEMONS_URL = 'https://pokeapi.co/api/v2/pokemon/bulbasaur';
const RATICATE_POKEMONS_URL = 'https://pokeapi.co/api/v2/pokemon/raticate';
const KAKUNA_POKEMONS_URL = 'https://pokeapi.co/api/v2/pokemon/kakuna';
let promise_1 = getPromise(BULBASAUR_POKEMONS_URL);
let promise_2 = getPromise(RATICATE_POKEMONS_URL);
let promise_3 = getPromise(KAKUNA_POKEMONS_URL);
E usiamo il metodo Promise.all() passando un array di promise.
Promise.all([promise_1, promise_2, promise_3]).then(result => {
console.log({result});
}).catch(error => {
console.log('An Error Occured');
});
Risultato:

Come puoi vedere, viene restituito il risultato di tutte le promise. Il tempo di esecuzione di tutte le promise è uguale al tempo massimo di esecuzione della promise più "lenta".
Il metodo Promise.any()
Promise.any([promise])
- Simile al metodo all()
, anche .any()
accetta un array di promise da eseguire in parallelo. Però questo metodo non aspetta che tutte le promise siano risolte. Termina quando una qualsiasi delle promise è conclusa.
Promise.any([promise_1, promise_2, promise_3]).then(result => {
console.log(JSON.parse(result));
}).catch(error => {
console.log('An Error Occured');
});
Il risultato sarà il risultato di una delle promise risolte:

Il metodo Promise.allSettled()
Promise.allSettled([promise])
- Questo metodo aspetta che tutte le promise siano concluse (risolte o rifiutate) e ritorna i loro risultati come un array di oggetti. I risultati conterranno la stato (fulfilled/rejected) e il valore, se lo stato è fulfilled. Nel caso in cui lo stato sia rejected, ritornerà la ragione dell'errore.
Ecco un esempio di tutte promise con stato fulfilled:
Promise.allSettled([promise_1, promise_2, promise_3]).then(result => {
console.log({result});
}).catch(error => {
console.log('There is an Error!');
});
Risultato:

Se qualcuna delle promise fosse rifiutata, per esempio la promise_1,
let promise_1 = getPromise(POKEMONS_BAD_URL);

Il metodo Promise.race()
Promise.race([promises])
– Aspetta la prima promise (la più veloce) a essere conclusa, e ritorna il risultato/errore di conseguenza.
Promise.race([promise_1, promise_2, promise_3]).then(result => {
console.log(JSON.parse(result));
}).catch(error => {
console.log('An Error Occured');
});
Il risultato sarà la promise che viene risolta più rapidamente:

I metodi Promise.resolve/reject
Promise.resolve(value)
– Risolve una promise con il valore passato come argomento. È equivalente al seguente codice:
let promise = new Promise(resolve => resolve(value));
Promise.reject(error)
– Rifiuta una promise con l'errore passato come argomento. È equivalente al seguente codice:
let promise = new Promise((resolve, reject) => reject(error));
Possiamo riscrivere l'esempio di PizzaHub con le promise?
Certo che sì, facciamolo. Supponiamo che il metodo query
ritorni una promise. Nella vita reale, questo metodo potrebbe comunicare con un database e restituire i risultati. In questo caso, restituisce un risultato predeterminato, ma va bene per il nostro esempio.
function query(endpoint) {
if (endpoint === `/api/pizzahub/`) {
return new Promise((resolve, reject) => {
resolve({'shopId': '123'});
})
} else if (endpoint.indexOf('/api/pizzahub/pizza/') >=0) {
return new Promise((resolve, reject) => {
resolve({pizzas: [{'type': 'veg', 'name': 'margherita', 'id': '123'}]});
})
} else if (endpoint.indexOf('/api/pizzahub/beverages') >=0) {
return new Promise((resolve, reject) => {
resolve({id: '10', 'type': 'veg', 'name': 'margherita', 'beverage': 'coke'});
})
} else if (endpoint === `/api/order`) {
return new Promise((resolve, reject) => {
resolve({'type': 'veg', 'name': 'margherita', 'beverage': 'coke'});
})
}
}
Il prossimo passo è il refactoring del nostro inferno di callback. A questo scopo, per prima cosa, creeremo alcune funzioni logiche:
// Restituisce shop id
let getShopId = result => result.shopId;
// Restituisce una promise con la liste delle pizze del ristorante
let getPizzaList = shopId => {
const url = `/api/pizzahub/pizza/${shopId}`;
return query(url);
}
// Restituisce una promise con la pizza che corrisponde alle richieste del cliente
let getMyPizza = (result, type, name) => {
let pizzas = result.pizzas;
let myPizza = pizzas.find((pizza) => {
return (pizza.type===type && pizza.name===name);
});
const url = `/api/pizzahub/beverages/${myPizza.id}`;
return query(url);
}
// Restituisce una promise dopo aver ordinato
let performOrder = result => {
let beverage = result.id;
return query(`/api/order`, {'type': result.type, 'name': result.name, 'beverage': result.beverage});
}
// Conferma l'ordine
let confirmOrder = result => {
console.log(`Your order of ${result.type} ${result.name} with ${result.beverage} has been placed!`);
}
Usiamo queste funzioni per creare le promise necessarie. Questo è il codice che dovresti paragonare con l'esempio dell'inferno di callback. È molto più bello ed elegante.
function orderPizza(type, name) {
query(`/api/pizzahub/`)
.then(result => getShopId(result))
.then(shopId => getPizzaList(shopId))
.then(result => getMyPizza(result, type, name))
.then(result => performOrder(result))
.then(result => confirmOrder(result))
.catch(function(error){
console.log(`Bad luck, No Pizza for you today!`);
})
}
E infine, chiamiamo il metodo orderPizza() passando il tipo e il nome della pizza, in questo modo:
orderPizza('veg', 'margherita');
Cosa viene dopo?
Se sei arrivato fin qui e hai letto la maggior parte di questo articolo, congratulazioni! Dovresti avere un'idea più chiara sulle promise in JavaScript. Puoi trovare tutti gli esempi usati nell'articolo in questo repository GitHub.
Il prossimo passo, dovrebbe essere apprendere le funzioni async
in JavaScript che semplificano ulteriormente le cose. Il concetto delle promise in JavaScript si apprende meglio scrivendo dei piccoli esempi e lavorando su di essi.
A prescindere dal framework o dalla libreria che usiamo (Angular, React, Vue, ecc.), le operazioni asincrone sono inevitabili. Questo vuol dire che dobbiamo capire le promise per lavorare meglio.
Inoltre, sono sicuro che l'uso del metodo fetch
ti risulterà più facile adesso:
fetch('/api/user.json')
.then(function(response) {
return response.json();
})
.then(function(json) {
console.log(json); // {"name": "tapas", "blog": "freeCodeCamp"}
});
- Il metodo
fetch
restituisce una promise. Quindi possiamo usare il metodo.then
. - Il resto riguarda la catena di promise che abbiamo imparato in questo articolo.
Prima di terminare...
Grazie per aver letto fin qui! Connettiamoci. Puoi taggarmi su Twitter (@tapasadhikary) in un commento.
Potresti trovare interessanti anche questi articoli:
- JavaScript undefined and null: Let's talk about it one last time!
- JavaScript: Equality comparison with ==, === and Object.is
- The JavaScript `this` Keyword + 5 Key Binding Rules Explained for JS Beginners
- JavaScript TypeOf – How to Check the Type of a Variable or Object in JS
È tutto per adesso. Ci vediamo presto con il mio prossimo articolo. Stammi bene.