[Medium] ๐ Promise
Cos'รจ una Promise?โ
Promise รจ una funzionalitร ES6 introdotta principalmente per risolvere il callback hell e rendere il codice asincrono piรน facile da leggere e mantenere. Una Promise rappresenta il completamento (o fallimento) eventuale di un'operazione asincrona e il suo valore risultante.
Una Promise ha tre stati:
- pending: stato iniziale
- fulfilled: operazione completata con successo
- rejected: operazione fallita
Uso di base (Basic Usage)โ
Creare una Promiseโ
const myPromise = new Promise((resolve, reject) => {
// operazione asincrona
const success = true;
if (success) {
resolve('Successo!'); // la Promise diventa fulfilled
} else {
reject('Fallito!'); // la Promise diventa rejected
}
});
myPromise
.then((result) => {
console.log(result); // 'Successo!'
})
.catch((error) => {
console.log(error); // 'Fallito!'
});
Esempio reale: gestione delle richieste APIโ
// funzione condivisa per le richieste API
function fetchData(url) {
return fetch(url)
.then((response) => {
// controlla se la risposta รจ nell'intervallo 200~299
if (!response.ok) {
throw new Error('La risposta di rete non รจ stata ok ' + response.statusText);
}
return response.json(); // converte la risposta in JSON e la restituisce
})
.catch((error) => {
// gestisce problemi di rete o fallimenti delle richieste
console.log('Si รจ verificato un problema con la tua operazione fetch:', error);
throw error; // rilancia per la gestione a monte
});
}
fetchData('https://jsonplaceholder.typicode.com/users/1')
.then((userData) => {
console.log('Dati utente ricevuti:', userData);
})
.catch((error) => {
console.log('Errore:', error.message);
});
Metodi di Promiseโ
.then() / .catch() / .finally()โ
promise
.then((result) => {
// gestisci il successo
return result;
})
.catch((error) => {
// gestisci l'errore
console.error(error);
})
.finally(() => {
// viene eseguito indipendentemente dal successo o fallimento
console.log('Promise completata');
});
Promise.all()โ
Si risolve quando tutte le Promise si risolvono, e viene rifiutata immediatamente quando una qualsiasi Promise viene rifiutata.
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve) =>
setTimeout(() => resolve('foo'), 100)
);
const promise3 = Promise.resolve(42);
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values); // [3, 'foo', 42]
});
Quando usarla: procedere solo dopo che multiple chiamate API hanno tutte avuto successo.
Promise.race()โ
Restituisce il risultato della prima Promise che si risolve (fulfilled o rejected).
const promise1 = new Promise((resolve) =>
setTimeout(() => resolve('primo'), 500)
);
const promise2 = new Promise((resolve) =>
setTimeout(() => resolve('secondo'), 100)
);
Promise.race([promise1, promise2]).then((value) => {
console.log(value); // 'secondo' (piรน veloce)
});
Quando usarla: gestione del timeout delle richieste, o prendere la risposta piรน veloce.
Promise.allSettled()โ
Attende che tutte le Promise si risolvano (fulfilled/rejected), poi restituisce tutti i risultati.
const promise1 = Promise.resolve(3);
const promise2 = Promise.reject('Errore');
const promise3 = Promise.resolve(42);
Promise.allSettled([promise1, promise2, promise3]).then((results) => {
console.log(results);
// [
// { status: 'fulfilled', value: 3 },
// { status: 'rejected', reason: 'Errore' },
// { status: 'fulfilled', value: 42 }
// ]
});
Quando usarla: quando hai bisogno di tutti i risultati, anche se alcuni falliscono.
Promise.any()โ
Si risolve con la prima Promise fulfilled. Viene rifiutata solo se tutte le Promise vengono rifiutate.
const promise1 = Promise.reject('Errore 1');
const promise2 = new Promise((resolve) =>
setTimeout(() => resolve('Successo'), 100)
);
const promise3 = Promise.reject('Errore 2');
Promise.any([promise1, promise2, promise3]).then((value) => {
console.log(value); // 'Successo'
});
Quando usarla: risorse di fallback dove un solo successo รจ sufficiente.
Domande di colloquio (Interview Questions)โ
Domanda 1: concatenamento di Promise e gestione degli erroriโ
Prevedi l'output:
Promise.resolve(1)
.then((x) => x + 1)
.then(() => {
throw new Error('My Error');
})
.catch((e) => 1)
.then((x) => x + 1)
.then((x) => console.log(x))
.catch((e) => console.log('Questo non verrร eseguito'));
Analisi passo passoโ
Promise.resolve(1) // restituisce 1
.then((x) => x + 1) // x = 1, restituisce 2
.then(() => {
throw new Error('My Error'); // lancia -> va al catch
})
.catch((e) => 1) // cattura e restituisce il valore normale 1
.then((x) => x + 1) // x = 1, restituisce 2
.then((x) => console.log(x)) // stampa 2
.catch((e) => console.log('Questo non verrร eseguito')); // non eseguito
Risposta: 2
Concetti chiaveโ
catchpuรฒ recuperare con un valore normale: quandocatch()restituisce un valore normale, la catena continua in modalitร fulfilled.thendopocatchviene comunque eseguito: perchรฉ l'errore รจ stato gestito.- Il
catchfinale non viene eseguito: nessun nuovo errore รจ stato lanciato.
Se vuoi che l'errore continui a propagarsi, rilancialo nel catch:
Promise.resolve(1)
.then((x) => x + 1)
.then(() => {
throw new Error('My Error');
})
.catch((e) => {
console.log('Errore catturato');
throw e; // rilancia
})
.then((x) => x + 1) // non verrร eseguito
.then((x) => console.log(x)) // non verrร eseguito
.catch((e) => console.log('Questo verrร eseguito')); // verrร eseguito
Domanda 2: Event Loop e ordine di esecuzioneโ
Questa domanda testa anche la comprensione dell'Event Loop.
Prevedi l'output:
function a() {
console.log('Warlock');
}
function b() {
console.log('Druid');
Promise.resolve().then(() => {
console.log('Rogue');
});
}
function c() {
console.log('Mage');
}
function d() {
setTimeout(c, 100);
const temp = Promise.resolve().then(a);
console.log('Warrior');
setTimeout(b, 0);
}
d();
Capire l'ordine in d()โ
function d() {
setTimeout(c, 100); // 4. macro task (100ms di ritardo)
const temp = Promise.resolve().then(a); // 2. micro task (dopo il codice sincrono)
console.log('Warrior'); // 1. codice sincrono
setTimeout(b, 0); // 3. macro task (0ms, comunque macro)
}
Ordine di esecuzione:
- Codice sincrono:
console.log('Warrior')->Warrior - Micro task:
Promise.resolve().then(a)-> eseguea()->Warlock - Macro task:
setTimeout(b, 0)viene eseguito per primo- esegue
b()->Druid - dentro
b,Promise.resolve().then(...)รจ un micro task ->Rogue
- Macro task:
setTimeout(c, 100)viene eseguito dopo ->Mage
Rispostaโ
Warrior
Warlock
Druid
Rogue
Mage
Concetti chiaveโ
- Codice sincrono > Micro task (
Promise) > Macro task (setTimeout) - I callback
.then()sono micro task: vengono eseguiti dopo il macro task corrente, prima del prossimo macro task setTimeout(..., 0)รจ comunque un macro task e viene eseguito dopo i micro task
Domanda 3: comportamento sincrono vs asincrono del costruttore Promiseโ
Prevedi l'output:
function printing() {
console.log(1);
setTimeout(function () {
console.log(2);
}, 1000);
setTimeout(function () {
console.log(3);
}, 0);
new Promise((resolve, reject) => {
console.log(4);
resolve(5);
}).then((foo) => {
console.log(6);
});
console.log(7);
}
printing();
// output ?
Dettaglio importanteโ
Il punto chiave: il codice dentro il costruttore della Promise viene eseguito in modo sincrono.
Solo i callback .then() / .catch() sono asincroni.
Analisi dell'esecuzione:
console.log(1); // 1. sincrono
setTimeout(() => console.log(2), 1000); // 5. macro task (1000ms)
setTimeout(() => console.log(3), 0); // 4. macro task (0ms)
new Promise((resolve, reject) => {
console.log(4); // 2. sincrono (dentro il costruttore)
resolve(5);
}).then((foo) => {
console.log(6); // micro task
});
console.log(7); // 3. sincrono
Flusso di esecuzione:
- Sincrono: 1 -> 4 -> 7
- Micro task: 6
- Macro task (per ritardo): 3 -> 2
Rispostaโ
1
4
7
6
3
2
Concetti chiaveโ
- Il corpo del costruttore Promise รจ sincrono:
console.log(4)non รจ asincrono. - Solo
.then()e.catch()sono micro task asincroni. - Ordine: codice sincrono -> micro task -> macro task.
Insidie comuni (Common Pitfalls)โ
1. Dimenticare il returnโ
Se dimentichi return in una catena di Promise, il .then() successivo riceve undefined.
// โ sbagliato
fetchUser()
.then((user) => {
fetchPosts(user.id); // dimenticato il return
})
.then((posts) => {
console.log(posts); // undefined
});
// โ
corretto
fetchUser()
.then((user) => {
return fetchPosts(user.id);
})
.then((posts) => {
console.log(posts); // dati corretti
});
2. Dimenticare di catturare gli erroriโ
Le rejection non gestite delle Promise possono interrompere il flusso e creare errori di runtime rumorosi.
// โ potrebbe causare rejection non gestite
fetchData()
.then((data) => {
return processData(data);
})
.then((result) => {
console.log(result);
});
// โ
aggiungi catch
fetchData()
.then((data) => {
return processData(data);
})
.then((result) => {
console.log(result);
})
.catch((error) => {
console.error('Si รจ verificato un errore:', error);
});
3. Uso eccessivo di new Promise(...)โ
Non avvolgere una funzione che giร restituisce una Promise.
// โ incapsulamento inutile
function fetchData() {
return new Promise((resolve, reject) => {
fetch(url)
.then((response) => resolve(response))
.catch((error) => reject(error));
});
}
// โ
restituisci direttamente
function fetchData() {
return fetch(url);
}
4. Concatenamento errato di catch multipliโ
Ogni catch() gestisce gli errori dalle parti precedenti della catena.
Promise.resolve()
.then(() => {
throw new Error('Errore 1');
})
.catch((e) => {
console.log('Catturato:', e.message); // Catturato: Errore 1
})
.then(() => {
throw new Error('Errore 2');
})
.catch((e) => {
console.log('Catturato:', e.message); // Catturato: Errore 2
});
Argomenti correlatiโ
- async/await - zucchero sintattico piรน pulito per Promise
- Event Loop - comprensione piรน profonda del modello asincrono