back to top

Perché JavaScript è asincrono?

JavaScript è un linguaggio single-threaded che viene eseguito in un unico thread. Questo significa che, se eseguisse tutte le operazioni in modo strettamente sequenziale (sincrono), il codice rischierebbe di bloccarsi per operazioni lente (come richieste di rete o letture da file).
Per risolvere questo problema, JavaScript adotta un modello di concorrenza basato sugli event loop e sulle callback. Questo meccanismo gli consente di delegare alcune operazioni a thread “esterni” (come quelli del browser o del sistema), mentre l’esecuzione principale prosegue senza bloccare l’utente.


L’evoluzione del codice asincrono: dalle callback alle Promesse

Le Callback

In passato, la gestione del codice asincrono si basava su funzioni di callback, ossia funzioni passate come parametro ad altre funzioni, che venivano eseguite al completamento di una determinata operazione. Tuttavia, ciò portava facilmente al fenomeno noto come callback hell:

operazione1(function(risultato1) {
  operazione2(risultato1, function(risultato2) {
    operazione3(risultato2, function(risultato3) {
      // Ecc...
    });
  });
});

La leggibilità del codice ne risentiva, così come la gestione degli errori, che diventava complicata.

Le Promesse

Per risolvere queste criticità, JavaScript (dalla specifica ECMAScript 2015) ha introdotto le Promises. Una promessa rappresenta il risultato di un’operazione asincrona, e può trovarsi in uno di tre stati:

  1. Pending (in attesa): la promessa è in corso di esecuzione, non ha ancora restituito un risultato né un errore.
  2. Fulfilled (risolta con successo): l’operazione asincrona è terminata con esito positivo.
  3. Rejected (rifiutata): l’operazione è terminata con errore o esito negativo.

Il grande vantaggio delle promesse è la possibilità di “catenarle” e gestire gli errori in maniera più intuitiva. Esempio:

function fetchData(url) {
  return new Promise((resolve, reject) => {
    // Simuliamo un’operazione asincrona (es. chiamata fetch)
    setTimeout(() => {
      const success = true; // ipotizziamo che vada tutto bene
      if (success) {
        resolve("Dati ottenuti correttamente!");
      } else {
        reject("Errore durante il recupero dei dati!");
      }
    }, 1000);
  });
}

fetchData("http://esempio.com/api")
  .then((dati) => {
    console.log(dati);  // "Dati ottenuti correttamente!"
    return "Nuovi dati elaborati...";
  })
  .then((valoreSuccessivo) => {
    console.log(valoreSuccessivo);
  })
  .catch((errore) => {
    console.error("Si è verificato un errore:", errore);
  });
  • then() viene invocato in caso di successo. Può ritornare un nuovo valore (o un’altra Promessa) per continuare la catena.
  • catch() cattura eventuali errori in una delle promesse precedenti.

Async/Await: la sintassi più pulita per il codice asincrono

A partire da ECMAScript 2017, JavaScript ha introdotto le keyword async e await, che permettono di scrivere codice asincrono in uno stile simile al sincrono, migliorandone la leggibilità.

Come funziona async?

  • Quando dichiari una funzione con la keyword async, quella funzione restituisce sempre una Promessa.
  • All’interno di una funzione async puoi usare la keyword await per “aspettare” il completamento di un’operazione asincrona (una Promessa).

Come funziona await?

  • await sospende l’esecuzione della funzione async fino a quando la Promessa passata viene risolta o rigettata.
  • Se la Promessa viene risolta, await restituisce il valore di successo.
  • Se viene rigettata, viene generata un’eccezione che può essere gestita con un blocco try/catch.

Ecco lo stesso esempio precedente, ma riscritto usando async/await:

function fetchData(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const success = true;
      if (success) {
        resolve("Dati ottenuti correttamente!");
      } else {
        reject("Errore durante il recupero dei dati!");
      }
    }, 1000);
  });
}

async function getData() {
  try {
    const dati = await fetchData("http://esempio.com/api");
    console.log(dati); // "Dati ottenuti correttamente!"
    
    // Possiamo usare i dati o richiamare un'altra funzione asincrona
    const risultatoSuccessivo = await fetchData("http://esempio.com/altri-dati");
    console.log(risultatoSuccessivo);
  } catch (errore) {
    console.error("Si è verificato un errore:", errore);
  }
}

// Invocazione
getData();

Notate come, grazie a await, il codice risulta più lineare e simile allo stile sincrono. Anche la gestione degli errori è semplificata dal blocco try/catch.


Vantaggi di Async/Await

  1. Leggibilità del Codice: la scrittura di codice asincrono risulta più intuitiva e meno annidata.
  2. Debug e Manutenzione: la gestione degli errori con try/catch semplifica il flusso e rende il debugging più immediato.
  3. Flessibilità: si può facilmente mischiare then() e catch() con le funzioni async, anche se si consiglia di rimanere coerenti con uno stile.

Best Practice

  1. Gestione degli Errori: racchiudi sempre le operazioni asincrone in blocchi try/catch o utilizza .catch().
  2. Parallelizzazione con Promise.all(): se hai più operazioni indipendenti, puoi eseguirle in parallelo:jsCopiaModificaasync function getMultipleData() { try { const [risposta1, risposta2] = await Promise.all([ fetchData("url1"), fetchData("url2") ]); console.log(risposta1, risposta2); } catch (error) { console.error("Errore in una delle chiamate:", error); } }
  3. Handling di Errori Specifici: in alcuni scenari complessi, potresti voler gestire errori diversi in base alla chiamata che li genera. In tal caso, potresti eseguire più blocchi try/catch o gestire i singoli .catch() di ogni promessa.
  4. Evitare di Bloccare il Thread: non usare await in luoghi dove eseguire operazioni in parallelo sarebbe più efficiente. Valuta sempre se puoi usare Promise.all() o Promise.race().
  5. Non trascurare la Compatibilità: se devi supportare browser più vecchi, valuta un transpiler (come Babel) o un polyfill per async/await.

Conclusioni

Le Promesse e, successivamente, la sintassi Async/Await hanno rivoluzionato il modo di scrivere codice asincrono in JavaScript. Grazie a queste tecniche, il codice diventa più lineare, leggibile e semplice da mantenere, riducendo gli annidamenti tipici delle callback. Saper padroneggiare queste caratteristiche è fondamentale per scrivere applicazioni JavaScript moderne, performanti e manutenibili.

Punti chiave da ricordare:

  • Le callback sono alla base dell’asincronia in JS, ma possono portare a codice complesso (callback hell).
  • Le Promesse risolvono in parte la questione, introducendo un flusso più chiaro.
  • async/await semplifica ulteriormente la scrittura, rendendola più vicina allo stile sincrono.
  • Gestire correttamente gli errori e ottimizzare le chiamate in parallelo sono elementi essenziali per un buon uso di queste tecniche.

Sperimentare e creare piccoli prototipi è il metodo migliore per familiarizzare con la programmazione asincrona. Buon coding!


Hai trovato utile questo Articolo? Continua a seguire Cyberalchimista sui social (@cyberalchimista) per altri tutorial e consigli sullo sviluppo web! Se hai domande o vuoi condividere i tuoi progetti contattaci.

Condividi

Articoli Recenti

Categorie popolari