Salta al contenuto principale

Gestire l'Asincronia in JavaScript: Promises e Async/Await

Profile picture for user luca77king

Prima dell'avvento delle Promises, la gestione dell'asincronia era affidata principalmente ai callback, una tecnica che, seppur funzionale, portava a un codice detto "callback hell", un groviglio di funzioni annidate che rendevano il codice difficile da leggere, manutenere e debugare. Immaginate una situazione in cui dovete effettuare tre chiamate asincrone a un server: ottenere dati utente, poi ottenere i loro ordini e infine calcolare il totale degli acquisti. Con i callback, il codice potrebbe assomigliare a una piramide rovesciata, con ogni callback annidata dentro la precedente, una situazione decisamente poco elegante e facilmente soggetta ad errori.

Le Promises rappresentano un significativo miglioramento rispetto ai callback. Una Promise è un oggetto che rappresenta il risultato eventuale di un'operazione asincrona. Può trovarsi in uno di tre stati: pending (in attesa), fulfilled (risolta con successo) o rejected (rifiutata, a causa di un errore). Invece di passare una funzione callback ad ogni passo del processo, una Promise permette di gestire il risultato finale – successo o fallimento – in modo più strutturato e leggibile, utilizzando i metodi .then() e .catch().

Il metodo .then() viene chiamato quando la Promise è risolta con successo. Accetta una funzione che processa il valore restituito dalla Promise. Possiamo concatenare più .then() per gestire una sequenza di operazioni asincrone in modo lineare e più comprensibile. Il metodo .catch(), invece, viene chiamato se la Promise viene rifiutata, permettendo di gestire gli errori in modo centralizzato, evitando la proliferazione di blocchi try...catch in ogni singolo callback.

Ad esempio, invece della piramide di callback per ottenere i dati utente, gli ordini e il totale degli acquisti, usando le Promises avremmo un codice più pulito e leggibile:

function getUserData() {
  return new Promise((resolve, reject) => {
    // Simulazione di una chiamata asincrona
    setTimeout(() => {
      const userData = { id: 1, name: "Mario Rossi" };
      resolve(userData);
    }, 1000);
  });
}

function getUserOrders(userId) {
  return new Promise((resolve, reject) => {
    // Simulazione di una chiamata asincrona
    setTimeout(() => {
      const userOrders = [{ amount: 10 }, { amount: 20 }];
      resolve(userOrders);
    }, 1000);
  });
}

function calculateTotal(orders) {
  return orders.reduce((total, order) => total + order.amount, 0);
}

getUserData()
  .then(userData => getUserOrders(userData.id))
  .then(orders => calculateTotal(orders))
  .then(total => console.log("Totale acquisti:", total))
  .catch(error => console.error("Errore:", error));

Questo codice è molto più leggibile e gestibile rispetto a una soluzione con callback annidati. La sequenza delle operazioni è chiara e la gestione degli errori è centralizzata.

Tuttavia, anche con le Promises, il codice può diventare complesso se si hanno molte operazioni asincrone da gestire in sequenza. Qui entra in gioco async/await, una sintassi più moderna e intuitiva introdotta in ES2017, che semplifica ulteriormente la gestione dell'asincronia.

async/await rende il codice asincrono molto simile a quello sincrono, migliorando la leggibilità e la manutenibilità. Una funzione dichiarata con la parola chiave async restituisce sempre una Promise. All'interno di una funzione async, possiamo usare await prima di una Promise. Quando il codice incontra await, la funzione sospende l'esecuzione finché la Promise non viene risolta, quindi riprende l'esecuzione con il valore restituito dalla Promise.

Riscrivendo l'esempio precedente con async/await:

async function getTotalPurchases() {
  try {
    const userData = await getUserData();
    const orders = await getUserOrders(userData.id);
    const total = calculateTotal(orders);
    console.log("Totale acquisti:", total);
  } catch (error) {
    console.error("Errore:", error);
  }
}

getTotalPurchases();

Questo codice è incredibilmente più pulito e intuitivo. Sembra codice sincrono, ma gestisce in modo efficiente l'asincronia. L'uso di try...catch per gestire gli errori è semplice ed elegante.

In conclusione, le Promises e async/await sono strumenti essenziali per la gestione dell'asincronia in JavaScript. Mentre le Promises offrono una solida base per la gestione di operazioni asincrone, async/await le rende più facili da utilizzare e da leggere, portando a un codice più pulito, manutenibile e meno soggetto ad errori. La scelta tra le due dipende dal contesto e dalla complessità del progetto, ma la combinazione di entrambi offre la soluzione più efficace per scrivere codice JavaScript moderno ed efficiente.