JavaScript è a thread singolo, eppure gestisce migliaia di operazioni concorrenti: chiamate API, letture di file, timer, eventi utente. Ci riesce grazie alla programmazione asincrona. Comprendere le Promise e async/await è un requisito imprescindibile per qualsiasi sviluppatore JavaScript.

L'Evoluzione: Callback → Promise → Async/Await

L'Era dei Callback

Callback Hell
// I callback annidati diventano rapidamente illeggibili
getUser(userId, (err, user) => {
  if (err) return handleError(err);
  getOrders(user.id, (err, orders) => {
    if (err) return handleError(err);
    getOrderDetails(orders[0].id, (err, details) => {
      if (err) return handleError(err);
      console.log(details); // Finalmente!
    });
  });
});

Le Promise: Un Approccio Migliore

Catena di Promise
getUser(userId)
  .then(user => getOrders(user.id))
  .then(orders => getOrderDetails(orders[0].id))
  .then(details => console.log(details))
  .catch(err => handleError(err))
  .finally(() => hideSpinner());

Async/Await: Promise in Forma Leggibile

Async/Await
async function getFullOrder(userId) {
  try {
    const user = await getUser(userId);
    const orders = await getOrders(user.id);
    const details = await getOrderDetails(orders[0].id);
    return details;
  } catch (err) {
    handleError(err);
  } finally {
    hideSpinner();
  }
}

Creare Promise

Costruttore di Promise
function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

function fetchWithTimeout(url, ms) {
  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => reject(new Error("Timeout")), ms);

    fetch(url)
      .then(response => {
        clearTimeout(timer);
        resolve(response);
      })
      .catch(err => {
        clearTimeout(timer);
        reject(err);
      });
  });
}

Operazioni Concorrenti

Promise.all — Esecuzione in Parallelo
// Tutte devono riuscire (fallisce subito al primo rifiuto)
const [users, products, orders] = await Promise.all([
  fetch("/api/users").then(r => r.json()),
  fetch("/api/products").then(r => r.json()),
  fetch("/api/orders").then(r => r.json()),
]);

// allSettled — Non viene mai rifiutata, restituisce lo stato di ciascuna
const results = await Promise.allSettled([
  fetch("/api/users"),
  fetch("/api/products"),  // anche se questa fallisce...
  fetch("/api/orders"),    // ...otteniamo comunque i risultati delle altre
]);

results.forEach(result => {
  if (result.status === "fulfilled") {
    console.log("Successo:", result.value);
  } else {
    console.log("Fallita:", result.reason);
  }
});
Promise.race — Vince la Prima che Termina
// Utile per i timeout
const result = await Promise.race([
  fetch("/api/data"),
  delay(5000).then(() => { throw new Error("Timeout!"); })
]);

Pattern di Gestione degli Errori

Funzione Wrapper (stile Go)
// Restituisce una tupla [errore, dati] — niente try/catch necessario
async function to(promise) {
  try {
    const data = await promise;
    return [null, data];
  } catch (err) {
    return [err, null];
  }
}

// Utilizzo
const [err, user] = await to(getUser(123));
if (err) {
  console.error("Impossibile ottenere l'utente:", err.message);
  return;
}
console.log(user);

Errori Comuni

  • Dimenticare await: Senza await ottieni un oggetto Promise, non il suo valore risolto.
  • Sequenziale quando intendevi parallelo: await a(); await b(); viene eseguito in sequenza. Usa Promise.all([a(), b()]) per l'esecuzione in parallelo.
  • Rifiuti non gestiti: Aggiungi sempre un .catch() alle catene di Promise oppure usa try/catch con async/await.
  • Async nei cicli: forEach non attende. Usa for...of oppure Promise.all(arr.map(...)).
  • Creare Promise superflue: Se una funzione restituisce già una Promise, non incapsularla in new Promise().

Prova i Nostri Strumenti JavaScript Gratuiti

Minifica e formatta il tuo codice JavaScript all'istante.