Vai al contenuto principale

[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โ€‹

  1. catch puรฒ recuperare con un valore normale: quando catch() restituisce un valore normale, la catena continua in modalitร  fulfilled.
  2. then dopo catch viene comunque eseguito: perchรฉ l'errore รจ stato gestito.
  3. Il catch finale 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:

  1. Codice sincrono: console.log('Warrior') -> Warrior
  2. Micro task: Promise.resolve().then(a) -> esegue a() -> Warlock
  3. Macro task:
    • setTimeout(b, 0) viene eseguito per primo
    • esegue b() -> Druid
    • dentro b, Promise.resolve().then(...) รจ un micro task -> Rogue
  4. 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:

  1. Sincrono: 1 -> 4 -> 7
  2. Micro task: 6
  3. Macro task (per ritardo): 3 -> 2

Rispostaโ€‹

1
4
7
6
3
2

Concetti chiaveโ€‹

  1. Il corpo del costruttore Promise รจ sincrono: console.log(4) non รจ asincrono.
  2. Solo .then() e .catch() sono micro task asincroni.
  3. 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

Riferimentiโ€‹