Articolo originale: JavaScript Async/Await Tutorial – Learn Callbacks, Promises, and Async/Await in JS by Making Ice Cream 🍧🍨🍦

Oggi realizzeremo e gestiremo una gelateria e allo stesso tempo impareremo JavaScript asincrono. Strada facendo imparerai a usare:

  • Callback
  • Promise
  • async / await
Alt Text

Ecco quello di cui parleremo in questo articolo:

Cos'è JavaScript asincrono?

Alt Text

Se vuoi realizzare progetti in modo efficiente, questo concetto fa per te.

La teoria di JavaScript asincrono ti aiuta a suddividere dei progetti grandi e complessi in attività più semplici.

Poi puoi utilizzare tre tecniche – callback, promise o async/await – per eseguire queste attività più semplici in modo da ottenere i risultati migliori.

Iniziamo!🎖️

JavaScript sincrono vs asincrono

Alt Text

Cos'è un sistema sincrono?

In un sistema sincrono, le operazioni vengono completate una dopo l'altra.

Immagina di avere una sola mano per svolgere 10 azioni, in modo da doverle completare una alla volta.

Dai un'occhiata alla GIF 👇 – le operazioni avvengono una alla volta:

Synchronous System

Finché la prima immagine non è completamente caricata, non inizierà il caricamento della seconda.

JavaScript è sincrono (mono-thread) di default. Pensala così: un thread corrisponde a una mano con cui poter fare delle cose.

Cos'è un sistema asincrono?

In questo sistema, le operazioni sono svolte indipendentemente.

In questo caso, immagina che per le dieci azioni, hai 10 mani. Quindi, ogni mano può occuparsi indipendentemente di un'azione, in contemporanea alle altre.

Dai un'occhiata alla GIF 👇 – come puoi vedere, le immagini vengono caricate allo stesso tempo.

Asynchronous System

Di nuovo, tutte le immagini sono caricate secondo il proprio ritmo. Nessuna di loro aspetta le altre.

Per riassumere JS sincrono vs asincrono:

Quando tre immagini corrono una maratona:

  • In un sistema sincrono, le tre immagini sono nella stessa corsia. Un'immagine non può superarne un'altra. Finiscono la corsa una dopo l'altra e se l'immagine numero 2 si ferma, si ferma anche l'immagine seguente:
Alt Text
  • In un sistema asincrono, le tre immagini sono in corsie diverse. Finiscono la gara secondo il loro passo, senza doversi fermare a causa delle altre:
Alt Text

Esempi di codice sincrono e asincrono

Alt Text

Prima di iniziare con il nostro progetto, diamo un'occhiata ad alcuni esempi e togliamoci ogni dubbio.

Esempio di codice sincrono

Alt Text

Per testare un sistema sincrono, scrivi questo codice in JavaScript:

console.log(" Io ");

console.log(" mangio ");

console.log(" il gelato ");

Ecco il risultato nella console: 👇

image-11

Esempio di codice asincrono

Alt Text

Diciamo che ci vogliono due secondi per mangiare del gelato. Adesso, testiamo un sistema asincrono. Scrivi il codice JavaScript che trovi qui sotto.

Nota: non preoccuparti, parleremo della funzione setTimeout() più avanti in questo articolo.

console.log(" Io ");

// Il blocco qui sotto verrà mostrato dopo due secondi

setTimeout(()=>{
  console.log(" mangio ");
}, 2000);

console.log(" il gelato ")

Ed ecco il risultato nella console: 👇

image-12

Ora che sai la differenza tra le operazioni sincrone e asincrone, realizziamo la nostra gelateria.

Come impostare il progetto

Alt Text

Per questo progetto puoi aprire semplicemente Codepen.io e iniziare a scrivere il codice, oppure puoi usare VS code o un editor di tua scelta.

Apri la sezione JavaScript e poi la console da sviluppatore. Scriveremo il nostro codice e vedremo il risultato sulla console.

Cos'è un callback in JavaScript?

Alt Text

Un callback è una funzione usata come argomento di un'altra funzione.

Ecco un'illustrazione di un callback:

function callback(){
	// fa qualcosa
}

function fun(param){
	// fa qualcos'altro
    param();
}

fun(callback);

Non preoccuparti, tra poco vedremo degli esempi di callback.

Perché usiamo i callback?

Quando svolgiamo delle operazioni complesse, le suddividiamo in passaggi più semplici. Per fare in modo di stabilire delle relazioni tra questi passaggi in base al tempo (opzionale) e all'ordine, usiamo i callback.

Guarda questo esempio:👇

ic1

Questi sono i passaggi elementari che devi svolgere per fare il gelato. Nota che in questo esempio, l'ordine dei passaggi e il tempo sono cruciali. Non puoi semplicemente tagliare la frutta e servire il gelato.

Allo stesso tempo, se il passaggio precedente non è completato, non puoi andare al successivo.

Alt Text

Per spiegarlo più dettagliatamente, iniziamo a mettere su la nostra gelateria.

Ma un momento...

Alt Text

L'attività sarà divisa in due parti:

  • La dispensa che conterrà tutti gli ingredienti (il nostro back end)
  • La cucina in cui produrremo il gelato (il front end)
ic0

Memorizziamo i nostri dati

Adesso, dobbiamo memorizzare i nostri ingredienti in un oggetto. Iniziamo!

ic2-1

Puoi salvare gli ingredienti all'interno di un oggetto, in questo modo: 👇

let ingredienti = {
    frutta : ["fragola", "uva", "banana", "mela"]
 };

Gli altri ingredienti sono: 👇

ic3-1

Puoi inserire gli altri ingredienti in un oggetto JavaScript così: 👇

let ingredienti = {
  frutta : ["fragola", "uva", "banana", "mela"],
  contenitori : ["cono", "coppetta", "stecco"],
  topping : ["cioccolato", "granella"],
  altro : ["acqua", "ghiaccio"]
};

L'intera attività dipende dagli ordini dei clienti. Una volta che abbiamo un ordine, iniziamo la produzione e poi serviamo il gelato. Quindi, creeremo due funzioni ->

  • ordine
  • preparazione
Alt Text

Ecco come funziona il tutto: 👇

ic5

Creiamo le nostre funzioni. Utilizzeremo delle funzioni freccia:

let ordine = () =>{};

let preparazione = () =>{};

Adesso, stabiliamo una relazione tra le due funzioni usando un callback, in questo modo: 👇

let ordine = (chiamaPrep) =>{

  chiamaPrep();
};

let preparazione = () =>{};

Facciamo un piccolo test

Useremo la funzione console.log() per fare dei test, in modo da sciogliere ogni dubbio che potremmo avere su come abbiamo stabilito la relazione tra le due funzioni.

let ordine = (chiamaPrep) =>{

console.log("Ordine effettuato. Chiama preparazione")

// la funzione 👇 chiamata 
  chiamaPrep();
};

let preparazione = () =>{

console.log("La preparazione è iniziata")

};

Per eseguire il test, chiameremo la funzione ordine. E aggiungeremo la seconda funzione, chiamata preparazione come suo argomento.

// nome 👇 della seconda funzione
ordine(preparazione);

Ecco il risultato sulla console: 👇

image-13

Fai una pausa

Finora tutto bene – fai una pausa!

Alt Text

Ripuliamo il console.log

Tieni il codice e rimuovi il resto (non cancellare la variabile ingredienti). Nella prima funzione, passiamo un altro argomento in modo da poter ricevere l'ordine (il nome della frutta):

// funzione 1

let ordine = (nomeFrutta, chiamaPrep) =>{

  chiamaPrep();
};

// funzione 2

let preparazione = () =>{};


// chiamata 👇

ordine("", preparazione);

Ecco i nostri passaggi e il tempo necessario per ognuno di essi.

ic4-1

In questo diagramma, puoi vedere che il primo punto è effettuare l'ordine, per il quale servono 2 secondi. Il secondo punto è tagliare la frutta (2 secondi), il terzo è aggiungere acqua e ghiaccio (1 secondo), il quarto è avviare il macchinario (1 secondo), il quinto è selezionare il contenitore (2 secondi), il sesto è scegliere il topping (3 secondi) e il settimo e ultimo è servire il gelato (2 secondi).

Per stabilire il tempo, la funzione setTimeout() è perfetta, in quanto utilizza un callback prendendo una funzione come argomento.

ic6

Adesso, scegliamo la frutta e utilizziamo la funzione:

// funzione 1

let ordine = (nomeFrutta, chiamaPrep) =>{

  setTimeout(function(){

    console.log(`${ingredienti.frutta[nomeFrutta]} è stata selezionata`)

// Ordine effettuato. Chiama preparazione per iniziare
   chiamaPrep();
  },2000)
};

// funzione 2

let preparazione = () =>{
  // vuota per ora
};

// chiamata 👇
ordine(0, preparazione);

Ed ecco il risultato sulla console: 👇

image-14

Nota che il risultato viene mostrato dopo 2 secondi.

Se ti stai chiedendo come abbiamo scelto la fragola dalla variabile ingredienti, ecco la spiegazione: 👇

ic7

Non cancellare nulla. Adesso inizieremo a scrivere la funzione preparazione con il seguente codice.👇 Useremo delle funzioni freccia:

let preparazione = () =>{

  setTimeout(()=>{
    console.log("la preparazione è iniziata")
  },0000)

};

Ed ecco il risultato: 👇

image-15

Annideremo un'altra funzione setTimeout nella funzione setTimeout esistente per tagliare la frutta. In questo modo: 👇

let preparazione = () =>{
  
  setTimeout(()=>{
    console.log("la preparazione è iniziata");


    setTimeout(()=>{
      console.log("la frutta è stata tagliata");
    },2000);


  },0000);
};

Ed ecco il risultato: 👇

image-16

Se ricordi, questa è la nostra scaletta:

ic4-2

Completiamo la preparazione del gelato annidando una funzione all'interno di un'altra – si tratta di un callback, ricordi?

let preparazione = () =>{

  setTimeout(()=>{
    console.log("la preparazione è iniziata")
    setTimeout(()=>{
      console.log("la frutta è stata tagliata")
      setTimeout(()=>{
        console.log(`${ingredienti.altro[0]} e ${ingredienti.altro[1]} aggiunti`)
        setTimeout(()=>{
          console.log("avvio macchina")
          setTimeout(()=>{
            console.log(`gelato messo su ${ingredienti.contenitori[1]}`)
            setTimeout(()=>{
              console.log(`${ingredienti.topping[0]} come topping`)
              setTimeout(()=>{
                console.log("gelato servito")
              },2000)
            },3000)
          },2000)
        },1000)
      },1000)
    },2000)
  },0000)

};

Ed ecco il risultato sulla console: 👇

image-23

Ti senti confuso?

Alt Text

Questo si chiama "inferno callback". Assomiglia a qualcosa del genere (hai presente il codice qui sopra?): 👇

image-25

Come possiamo risolverlo?

Come usare le promise per sfuggire all'inferno callback

Alt Text

Le promise sono state inventate per risolvere il problema dell'inferno callback e per gestire meglio le operazioni.

Fai una pausa

Ma prima, fai una pausa!

Alt Text

Una promise ha questo aspetto:

image-26

Analizziamo insieme le promise.

Alt Text
image-27

Come mostrato dal diagramma qui sopra, una promise ha tre stati:

  • Pending: è lo stato iniziale. Nulla accade qui. È come se il tuo cliente stesse prendendo il suo tempo per ordinare, ma non ha ancora ordinato nulla.
  • Resolved (risolta): vuol dire che il cliente ha ricevuto il suo cibo ed è contento.
  • Rejected (respinta): vuol dire che il cliente non ha ricevuto il proprio ordine e ha lasciato il ristorante.

Usiamo le promise per il caso di studio della preparazione del nostro gelato.

Ma un momento...

Alt Text

Dobbiamo prima comprendere altre quattro cose ->

  • La relazione tra tempo e lavoro
  • Il concatenamento di promise
  • La gestione degli errori
  • Il gestore .finally

Iniziamo a creare la nostra gelateria e cerchiamo di comprendere ognuno di questi concetti, uno alla volta e a piccoli passi.

La relazione tra tempo e lavoro

Se ricordi, questi sono i passaggi per preparare il gelato, con il tempo necessario per ognuno.

ic4-3

Per renderlo possible, creiamo una variabile in JavaScript:👇

let gelateriaAperta = true;

Adesso creiamo una funzione chiamata ordine a cui passiamo due argomenti chiamati tempo e lavoro:

let ordine = ( lavoro, tempo ) =>{

  }

Ora, faremo una promessa al nostro cliente: "Ti serviremo il gelato"
In questo modo  ->

let ordine = ( lavoro, tempo ) =>{

  return new Promise( ( resolve, reject )=>{ } )

  }

La promise è composta da due parti:

  • Risolta [ gelato servito ]
  • Respinta [ il cliente non ha avuto il gelato ]
let ordine = ( lavoro, tempo ) => {

  return new Promise( ( resolve, reject )=>{

    if( gelateriaAperta ){

      resolve( )

    }

    else{

      reject( console.log("La gelateria è chiusa") )

    }

  })
}
Alt Text

Aggiungiamo i fattori tempo e lavoro nella nostra promise usando una funzione setTimeout() all'interno dell'istruzione if. Seguimi 👇

Nota: neanche nella vita reale puoi evitare il fattore tempo. Ciò è completamente dipendente dalla natura del tuo lavoro.

let ordine = ( lavoro, tempo ) => {

  return new Promise( ( resolve, reject )=>{

    if( gelateriaAperta ){

      setTimeout(()=>{

       // il lavoro 👇 viene svolto qui
        resolve( lavoro() )

// impostiamo 👇 qui il tempo per il lavoro
       }, tempo)

    }

    else{

      reject( console.log("La gelateria è chiusa") )

    }

  })
}

Utilizzeremo la funzione appena creata per iniziare la preparazione del gelato.

//passa 👇 qui una funzione per iniziare il lavoro
ordine(()=>console.log(`${ingredienti.frutta[0]} è stata selezionata`), 2000)
//                                                 imposta qui il tempo ☝️

Il risultato 👇 dopo 2 secondi sarà questo:

image-14

Ottimo lavoro!

Alt Text

Il concatenamento delle promise

Per questo metodo, definiamo ciò che dobbiamo fare quando la prima attività è completa usando il gestore .then.  Qualcosa del genere 👇

image-28

Il gestore .then restituisce una promise quando la promise originale viene risolta.

Ecco un esempio:

Alt Text

Facciamola semplice: è un po' come dare delle istruzioni a qualcuno. Dici a qualcuno "Fai prima questo, poi quest'altra cosa, poi questa, poi..." e così via.

  • La prima operazione è la promise originale.
  • Il resto delle operazioni restituisce la nostra promise una volta che una piccola porzione di codice è completata

Implementiamolo nel nostro progetto. In fondo al nostro codice scrivi le seguenti righe. 👇

Nota: non dimenticare di scrivere la parola return all'interno del gestore .then. Altrimenti non funzionerà appropriatamente. Se sei curioso, prova a rimuovere il return una volta terminati questi passaggi:

ordine(()=>console.log(`${ingredienti.frutta[0]} è stata selezionata`), 2000)

.then(()=>{
  return ordine(()=>console.log('la preparazione è iniziata'), 000)
})

Ed ecco il risultato: 👇

image-15

Finiamo il nostro progetto usando lo stesso sistema:👇

// step 1
ordine(()=>console.log(`${ingredienti.frutta[0]} è stata selezionata`), 2000)

// step 2
.then(()=>{
  return ordine(()=>console.log('la produzione è iniziata'), 000)
})

// step 3
.then(()=>{
  return ordine(()=>console.log("la frutta è stata tagliata"), 2000)
})

// step 4
.then(()=>{
  return ordine(()=>console.log(`${ingredienti.altro[0]} e ${ingredienti.altro[1]} aggiunti`), 1000)
})

// step 5
.then(()=>{
  return ordine(()=>console.log("avvio macchina"), 1000)
})

// step 6
.then(()=>{
  return ordine(()=>console.log(`gelato messo su ${ingredienti.contenitori[1]}`), 2000)
})

// step 7
.then(()=>{
  return ordine(()=>console.log(`${ingredienti.topping[0]} come topping`), 3000)
})

// Step 8
.then(()=>{
  return ordine(()=>console.log("gelato servito"), 2000)
})

Il risultato è: 👇

image-24

La gestione degli errori

Ci serve un modo per gestire gli errori quando qualcosa va storto, ma prima, dobbiamo capire il ciclo di una promise:

Alt Text
image-29

Per individuare gli errori, cambiamo il valore della nostra variabile in false.

let gelateriaAperta = false;

Questo vuol dire che la gelateria è chiusa. Non stiamo più vendendo gelato ai nostri clienti.

Per gestire questa situazione, usiamo il gestore .catch. Proprio come .then, restituisce anch'esso una promise, ma solo quando la promise originale è respinta.

Un piccolo promemoria:

  • .then funziona quando una promise è risolta (resolved)
  • .catch funziona quando una promise è respinta (rejected)

Vai in fondo e scrivi il seguente codice:👇

Ricorda soltanto che non dovrebbe esserci nulla tra il gestore .then precedente e il gestore .catch.

.catch(()=>{
  console.log("il cliente è andato via")
})

Questo è il risultato:👇

icecream1

Un paio di cose da notare su questo codice:

  • Il primo messaggio arriva dalla parte reject() della promise
  • Il secondo messaggio arriva dal gestore .catch

Come usare il gestore .finally()

Alt Text

Il gestore .finally funziona indipendentemente dal fatto che la promise sia risolta o respinta.

Ad esempio: possiamo servire 100 clienti o nessuno, ma la gelateria chiuderà comunque a fine giornata.

Se sei curioso, vai alla fine del codice e scrivi questo: 👇

.finally(()=>{
  console.log("fine giornata")
})

Il risultato:👇

icecream2

E adesso, date tutti il benvenuto ad async/await.

Come funzionano async/await in JavaScript?

Alt Text

Questo dovrebbe essere il modo migliore per scrivere promise e ci aiuta a mantenere il codice semplice e pulito.

Tutto ciò che devi fare è scrivere la parola async prima di una funzione regolare per farla diventare una promise.

Ma prima, fai una pausa!

Alt Text

Dai un'occhiata qui:👇

Alt Text

Promise vs async/await in JavaScript

Prima di async/await, per creare una promise avremmo scritto questo:

function ordine(){
   return new Promise( (resolve, reject) =>{

    // codice
   } )
}

Invece, usando async/await, possiamo farlo in questo modo:

//👇 la parola chiave magica
 async function ordine() {
    // codice
 }

Un momento...

Alt Text

Dobbiamo capire ->

  • Come usare le parole chiave try e catch
  • Come usare la parola chiave await

Come usare le parole chiave try e catch

Usiamo la parola chiave try per eseguire il codice, mentre usiamo catch in caso di errori. È lo stesso concetto che abbiamo visto per le promise.

Facciamo un confronto. Vedremo una piccola dimostrazione della sintassi, e poi inizieremo a scrivere del codice.

Promise in JS -> resolve o reject

Risolviamo o respingiamo una promise in questo modo:

function cucina(){

  return new Promise ((resolve, reject)=>{
    if(true){
       resolve("la promise è soddisfatta")
    }

    else{
        reject("c'è un errore")
    }
  })
}

cucina()  // esegui codice
.then()    // prossimo step
.then()    // prossimo step
.catch()   // c'è un errore
.finally() // fine della promise [optionale]

async / await in JS -> try, catch

Quando usiamo async/await, lo facciamo con questo formato:

//👇 parola chiave magica
async function cucina(){

   try{
// creiamo un problema fasullo      
      await abc;
   }

   catch(errore){
      console.log("abc non esiste", errore)
   }

   finally{
      console.log("il codice viene eseguito comunque")
   }
}

cucina()  // esegue codice

Niente panico, adesso parleremo della parola chiave await.

Come usare la parola chiave await in JS

Alt Text

La parola chiave await fa sì che JavaScript attenda fino a che il risultato della promise non è stabilito e lo restituisce.

Torniamo alla nostra gelateria. Non sappiamo quale topping preferisce un cliente – cioccolato o granella. Quindi abbiamo bisogno di stoppare il macchinario e andare a chiedere al cliente cosa preferisce sul suo gelato.

Nota che soltanto la cucina si è fermata, ma lo staff fuori dalla cucina sta ancora facendo cose come:

  • impiattare
  • pulire i tavoli
  • prendere ordini e così via.

Esempio di codice con la parola chiave await

Alt Text

Creiamo una  piccola promise per chiedere quale topping usare. Il processo impiega tre secondi.

function sceltaTopping (){
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{

      resolve( console.log("quale topping vorresti?") )

    },3000)
  })
}

Ora, creiamo la funzione cucina con la parola chiave async davanti.

async function cucina(){

  console.log("A")
  console.log("B")
  console.log("C")
  
  await sceltaTopping()
  
  console.log("D")
  console.log("E")

}

// chiamata della funzione

cucina();

Aggiungiamo altre operazioni sotto la chiamata di cucina().

console.log("impiattare")
console.log("pulire i tavoli")
console.log("prendere ordini")

Ed ecco il risultato:

image-19

Stiamo letteralmente andando fuori dalla cucina per chiedere al cliente "Quale topping vorresti?". Nel frattempo, vengono svolte altre operazioni.

Una volta che sappiamo quale topping ha scelto, entriamo in cucina e terminiamo il lavoro.

Una piccola nota

Insieme ad async/ await, puoi usare anche i gestori .then, .catch e .finally, che sono una parte centrale delle promise.

Apriamo di nuovo la gelateria

Alt Text

Creeremo due funzioni ->

  • cucina: per preparare il gelato
  • tempo: per assegnare il tempo necessario a svolgere ogni attività.

Partiamo dalla funzione time:

let gelateriaAperta = true;

function tempo(ms) {

   return new Promise( (resolve, reject) => {

      if(gelateriaAperta){
         setTimeout(resolve, ms);
      }

      else{
         reject(console.log("la gelateria è chiusa"))
      }
    });
}

E ora, la funzione cucina:

async function cucina(){
   try{

     // istruzioni
   }

   catch(errore){
    // gestione degli errori
   }
}

// chiamata
cucina();

Scriviamo delle piccole istruzioni e testiamo se la funzione cucina sta funzionando oppure no:

async function cucina(){
   try{

// tempo necessario per la prima operazione
     await tempo(2000)
     console.log(`${ingredienti.frutta[0]} è stata selezionata`)
   }

   catch(errore){
     console.log("il cliente è andato via")
   }

   finally{
      console.log("fine giornata, la gelateria chiude")
    }
}

// chiamata
cucina();

Quando  la gelateria è aperta, il risultato è simile a questo: 👇

image-20

Ed ecco il risultato quando la gelateria è chiusa: 👇

icecream3

Per ora tutto bene.

Alt Text

Completiamo il nostro progetto.

Ecco di nuovo la lista delle operazioni: 👇

ic4-4

Prima di tutto apriamo la gelateria:

let gelateriaAperta = true;

Ora scriviamo i passaggi nella funzione cucina(): 👇

async function cucina(){
    try{
	await tempo(2000)
	console.log(`${ingredienti.frutta[0]} è stata selezionata`)

	await tempo(0000)
	console.log("la preparazione è iniziata")

	await tempo(2000)
	console.log("la frutta è stata tagliata")

	await tempo(1000)
	console.log(`${ingredienti.altro[0]} e ${ingredienti.altro[1]} aggiunti`)

	await tempo(1000)
	console.log("avvio macchina")

	await tempo(2000)
	console.log(`gelato messo su ${ingredienti.contenitori[1]}`)

	await tempo(3000)
	console.log(`${ingredienti.topping[0]} come topping`)

	await tempo(2000)
	console.log("gelato servito")
    }

    catch(error){
	 console.log("il cliente è andato via")
    }
}

Ed ecco il risultato: 👇

image-22

Conclusione

Grazie per aver letto questo articolo fino alla fine! In questo tutorial hai imparato:

  • La differenza tra sistemi sincroni e asincroni
  • I meccanismi alla base di JavaScript asincrono, usando 3 tecniche (callback, promise e async/await)

Ed ecco la tua medaglia per aver letto fine alla fine. ❤️

Alt Text

Critiche e suggerimenti sono altamente apprezzati ❤️

Credits