</> HTML5Advent
ENFRESDEITPT

// js · Web Platform Advent #2

Le Promise in JavaScript: una guida pratica

Comprendi le promise in JavaScript — crearle, then/catch/finally, il concatenamento e combinarne molte con Promise.all, race, any e allSettled — con esempi eseguibili e le insidie più comuni.

Un pezzo di puzzle rosso che si incastra nell'ultimo spazio di un puzzle grigio

Una promise è un oggetto che rappresenta un valore non ancora pronto. È il fondamento del JavaScript asincrono: fetch(), i timer, le letture di file in Node e la maggior parte delle API moderne restituiscono promise. Una promise si trova sempre in uno di tre stati — in sospeso, mantenuta o rifiutata — e una volta risolta non cambia mai più.

Creare una promise

Il più delle volte consumi le promise che un'API ti fornisce. Ma puoi crearne una tu stesso con il costruttore Promise, che accetta una funzione executor che riceve resolve e reject:

const wait = (ms) => new Promise((resolve) => {
  setTimeout(resolve, ms);
});

const fetchUser = (id) => new Promise((resolve, reject) => {
  if (!id) reject(new Error('id is required'));
  else resolve({ id, name: 'Ada' });
});

Chiamare resolve(value) mantiene la promise con quel valore; chiamare reject(error) la rifiuta. Anche qualsiasi cosa lanciata in modo sincrono dentro l'executor rifiuta la promise.

then, catch e finally

Leggi l'esito di una promise con .then() per il successo, .catch() per il fallimento e .finally() per la pulizia che viene eseguita in entrambi i casi:

fetchUser(42)
  .then((user) => console.log('Got', user.name))
  .catch((err) => console.error('Failed:', err.message))
  .finally(() => console.log('Done'));

.then() può accettare due argomenti — onFulfilled e onRejected — ma un .catch() separato alla fine della catena è più chiaro e cattura anche gli errori lanciati all'interno dei gestori .then() precedenti.

Concatenamento: il vero superpotere

Ogni .then() restituisce una nuova promise, quindi si concatenano. Qualunque cosa restituisci da un gestore diventa l'input del successivo. Cosa fondamentale: se restituisci una promise, la catena attende che si risolva prima di proseguire:

fetch('/api/user')
  .then((res) => res.json())          // returns a promise → chain waits
  .then((user) => fetch('/api/posts?user=' + user.id))
  .then((res) => res.json())
  .then((posts) => console.log(posts.length, 'posts'))
  .catch((err) => console.error(err));

Un singolo .catch() alla fine gestisce un rifiuto proveniente da qualsiasi passaggio sopra di esso, ed è per questo che il concatenamento batte l'annidamento delle callback.

Velocisti di staffetta su una pista che si passano il testimone da un corridore al successivo
Come i corridori di una staffetta che si passano il testimone, una catena di promise consegna il risultato di ogni passaggio al gestore successivo, in sequenza.

Eseguire più promise insieme

Quando le attività non dipendono l'una dall'altra, avviale in parallelo e combina i risultati. Quattro metodi statici coprono i casi più comuni:

  • Promise.all([...]) — attende che tutte siano mantenute; viene rifiutata non appena una viene rifiutata. Restituisce un array di risultati in ordine.
  • Promise.allSettled([...]) — attende che tutte terminino e non viene mai rifiutata; restituisce un array di oggetti { status, value } o { status, reason }.
  • Promise.race([...]) — si risolve con la prima promise che si risolve, sia che venga mantenuta sia che venga rifiutata.
  • Promise.any([...]) — si risolve con la prima promise che viene mantenuta; viene rifiutata solo se tutte vengono rifiutate.
// Fetch three resources at once, fail if any fails
const [user, posts, prefs] = await Promise.all([
  fetch('/api/user').then((r) => r.json()),
  fetch('/api/posts').then((r) => r.json()),
  fetch('/api/prefs').then((r) => r.json()),
]);

// Same, but tolerate individual failures
const results = await Promise.allSettled([taskA(), taskB(), taskC()]);
results.forEach((r) => {
  if (r.status === 'fulfilled') console.log('ok', r.value);
  else console.warn('failed', r.reason);
});

Insidie comuni

  • Dimenticare il return. Dentro un .then(), se chiami una promise ma non la restituisci con return, la catena non la attenderà — perdi l'ordinamento e la gestione degli errori.
  • Rifiuti non gestiti. Una promise senza .catch() (o un try/catch attorno a await) produce un unhandledrejection. Gestisci sempre gli errori alla fine della catena.
  • Mescolare callback e promise. Non chiamare resolve due volte né entrambi resolve e reject — conta solo la prima chiamata; le altre vengono ignorate silenziosamente.
  • L'executor viene eseguito subito. La funzione che passi a new Promise() viene eseguita in modo sincrono, immediatamente — solo le callback di .then() vengono rinviate.

Riferimento rapido

ObiettivoMetodo
Gestire il successo.then(onFulfilled)
Gestire il fallimento.catch(onRejected)
Pulizia in entrambi i casi.finally(fn)
Tutte devono riuscirePromise.all
Raccogliere ogni esitoPromise.allSettled
La prima a risolversiPromise.race
La prima a riuscirePromise.any

Le promise sono l'impianto idraulico sotto async/await, che è solo una sintassi più gradevole sugli stessi oggetti. Una volta che il concatenamento e i quattro combinatori risultano naturali, il JavaScript asincrono smette di intimorire.