L’autore ha selezionato il Fondo per l’Internet Aperto/Libertà di Parola per ricevere una donazione come parte del programma Scrivi per le Donazioni.
Introduzione
Per molti programmi in JavaScript, il codice viene eseguito mentre lo sviluppatore lo scrive, riga per riga. Questo è chiamato esecuzione sincrona, perché le righe vengono eseguite una dopo l’altra, nell’ordine in cui sono state scritte. Tuttavia, non ogni istruzione che dai al computer deve essere eseguita immediatamente. Ad esempio, se invii una richiesta di rete, il processo che esegue il tuo codice dovrà attendere che i dati tornino prima di poterli elaborare. In questo caso, il tempo sarebbe sprecato se non eseguisse altro codice mentre aspetta che la richiesta di rete venga completata. Per risolvere questo problema, gli sviluppatori utilizzano la programmazione asincrona, in cui le righe di codice vengono eseguite in un ordine diverso da quello in cui sono state scritte. Con la programmazione asincrona, possiamo eseguire altro codice mentre aspettiamo che attività lunghe come le richieste di rete vengano completate.
Il codice JavaScript viene eseguito su un singolo thread all’interno di un processo informatico. Il suo codice viene elaborato in modo sincrono su questo thread, con solo un’istruzione eseguita alla volta. Pertanto, se dovessimo eseguire un’attività a lungo termine su questo thread, tutto il codice rimanente viene bloccato fino al completamento dell’attività. Sfruttando le funzionalità di programmazione asincrona di JavaScript, possiamo spostare le attività a lungo termine su un thread secondario per evitare questo problema. Quando l’attività è completata, il codice necessario per elaborare i dati dell’attività viene rimesso sul thread principale singolo.
In questo tutorial, imparerai come JavaScript gestisce le attività asincrone con l’aiuto del Ciclo degli eventi, che è una costruzione di JavaScript che completa una nuova attività mentre attende un’altra. Creerai quindi un programma che utilizza la programmazione asincrona per richiedere un elenco di film da un API di Studio Ghibli e salvare i dati in un file CSV. Il codice asincrono verrà scritto in tre modi: callbacks, promises e con le parole chiave async
/await
.
Nota: Alla data di scrittura di questo articolo, la programmazione asincrona non viene più eseguita solo utilizzando i callbacks, ma imparare questo metodo obsoleto può fornire un ottimo contesto su perché la comunità JavaScript ora utilizza le promises. Le parole chiave async
/await
ci consentono di utilizzare le promises in modo meno verboso e sono quindi il modo standard per eseguire la programmazione asincrona in JavaScript al momento della scrittura di questo articolo.
Prerequisiti
- Node.js installato sul tuo computer di sviluppo. Questo tutorial utilizza la versione 10.17.0. Per installarlo su macOS o Ubuntu 18.04, segui i passaggi in Come Installare Node.js e Creare un Ambiente di Sviluppo Locale su macOS o nella sezione Installazione Usando un PPA di Come Installare Node.js su Ubuntu 18.04.
- Dovrai anche essere familiare con l’installazione dei pacchetti nel tuo progetto. Mettiti al passo leggendo la nostra guida su Come Utilizzare i Moduli Node.js con npm e package.json.
- È importante che tu sia a tuo agio nella creazione ed esecuzione delle funzioni in JavaScript prima di imparare come usarle in modo asincrono. Se hai bisogno di un’introduzione o di un ripasso, puoi leggere la nostra guida su Come Definire le Funzioni in JavaScript
Il Ciclo degli Eventi
Il ciclo degli eventi
Iniziamo studiando il funzionamento interno dell’esecuzione delle funzioni JavaScript. Comprendere come si comporta ciò ti consentirà di scrivere codice asincrono in modo più deliberato e ti aiuterà nel debug del codice in futuro.
Mentre l’interprete JavaScript esegue il codice, ogni funzione chiamata viene aggiunta alla pila delle chiamate di JavaScript. La pila delle chiamate è una pila – una struttura dati simile a una lista in cui gli elementi possono essere aggiunti solo in cima e rimossi dalla cima. Le pile seguono il principio “Ultimo arrivato, primo ad uscire” o LIFO. Se aggiungi due elementi nella pila, l’elemento aggiunto più di recente viene rimosso per primo.
Illustriamo con un esempio usando la pila delle chiamate. Se JavaScript incontra una funzione functionA()
in fase di chiamata, viene aggiunta alla pila delle chiamate. Se quella funzione functionA()
chiama un’altra funzione functionB()
, allora functionB()
viene aggiunta in cima alla pila delle chiamate. Mentre JavaScript completa l’esecuzione di una funzione, questa viene rimossa dalla pila delle chiamate. Pertanto, JavaScript eseguirà prima functionB()
, la rimuoverà dalla pila quando sarà completa e quindi terminerà l’esecuzione di functionA()
e la rimuoverà dalla pila delle chiamate. Questo è il motivo per cui le funzioni interne vengono sempre eseguite prima delle loro funzioni esterne.
Quando JavaScript incontra un’operazione asincrona, come scrivere su un file, la aggiunge a una tabella nella sua memoria. Questa tabella memorizza l’operazione, la condizione per il suo completamento e la funzione da chiamare quando viene completata. Quando l’operazione viene completata, JavaScript aggiunge la funzione associata alla coda dei messaggi. Una coda è un’altra struttura dati simile a una lista in cui gli elementi possono essere solo aggiunti in fondo ma rimossi dall’alto. Nella coda dei messaggi, se due o più operazioni asincrone sono pronte per l’esecuzione delle loro funzioni, l’operazione asincrona che è stata completata per prima avrà la sua funzione contrassegnata per l’esecuzione per prima.
Le funzioni nella coda dei messaggi stanno aspettando di essere aggiunte allo stack delle chiamate. Il ciclo degli eventi è un processo perpetuo che controlla se lo stack delle chiamate è vuoto. Se lo è, allora il primo elemento nella coda dei messaggi viene spostato nello stack delle chiamate. JavaScript dà priorità alle funzioni nella coda dei messaggi rispetto alle chiamate di funzione che interpreta nel codice. L’effetto combinato dello stack delle chiamate, della coda dei messaggi e del ciclo degli eventi consente al codice JavaScript di essere elaborato mentre gestisce attività asincrone.
Ora che hai una comprensione a alto livello del ciclo degli eventi, sai come verrà eseguito il codice asincrono che scrivi. Con questa conoscenza, puoi ora creare codice asincrono con tre approcci diversi: callback, promesse e async
/await
.
Programmazione Asincrona con Callback
A callback function is one that is passed as an argument to another function, and then executed when the other function is finished. We use callbacks to ensure that code is executed only after an asynchronous operation is completed.
Per molto tempo, i callback erano il meccanismo più comune per scrivere codice asincrono, ma ora sono in gran parte diventati obsoleti perché possono rendere il codice difficile da leggere. In questo passaggio, scriverai un esempio di codice asincrono utilizzando i callback in modo che tu possa utilizzarlo come base per vedere l’efficienza aumentata di altre strategie.
Ci sono molti modi per utilizzare le funzioni di callback in un’altra funzione. In generale, assumono questa struttura:
Anche se non è richiesto sintatticamente da JavaScript o Node.js avere la funzione di callback come ultimo argomento della funzione esterna, è una pratica comune che rende i callback più facili da identificare. È anche comune per gli sviluppatori JavaScript utilizzare una funzione anonima come callback. Le funzioni anonime sono quelle create senza un nome. Di solito è molto più leggibile quando una funzione è definita alla fine dell’elenco degli argomenti.
Per dimostrare i callback, creiamo un modulo Node.js che scriva un elenco dei film dello Studio Ghibli su un file. Innanzitutto, crea una cartella che conterrà il nostro file JavaScript e il suo output:
Quindi entra in quella cartella:
Inizieremo facendo una richiesta HTTP all’API Studio Ghibli, che la nostra funzione di callback registrerà i risultati. Per fare ciò, installeremo una libreria che ci permetta di accedere ai dati di una risposta HTTP in un callback.
Nel terminale, inizializza npm così possiamo avere un riferimento per i nostri pacchetti in seguito:request
Quindi, installa la libreria request
:
Ora apri un nuovo file chiamato callbackMovies.js
in un editor di testo come nano
:
Nel tuo editor di testo, inserisci il seguente codice. Iniziamo inviando una richiesta HTTP con il modulo request
:
Nella prima riga, carichiamo il modulo request
che è stato installato tramite npm. Il modulo restituisce una funzione che può effettuare richieste HTTP; quindi salviamo quella funzione nella costante request
.
Quindi facciamo la richiesta HTTP utilizzando la funzione request()
. Ora stampiamo i dati della richiesta HTTP nella console aggiungendo le modifiche evidenziate:
Quando usiamo la funzione request()
, gli diamo due parametri:
- L’URL del sito web che stiamo cercando di richiedere
- A callback function that handles any errors or successful responses after the request is complete
La nostra funzione callback ha tre argomenti: error
, response
e body
. Quando la richiesta HTTP è completa, gli argomenti vengono automaticamente assegnati valori a seconda del risultato. Se la richiesta non è riuscita a inviare, allora error
conterrà un oggetto, ma response
e body
saranno null
. Se ha fatto la richiesta con successo, allora la risposta HTTP è memorizzata in response
. Se la nostra risposta HTTP restituisce dati (in questo esempio otteniamo JSON) allora i dati sono impostati in body
.
La nostra funzione di callback controlla prima se abbiamo ricevuto un errore. È una prassi ottimale controllare prima gli errori in una callback in modo che l’esecuzione della callback non continui con dati mancanti. In questo caso, registriamo l’errore e l’esecuzione della funzione. Successivamente controlliamo lo stato della risposta. Il nostro server potrebbe non essere sempre disponibile e le API possono cambiare, causando richieste una volta sensate diventino errate. Controllando che il codice di stato sia 200
, il che significa che la richiesta è stata “OK”, possiamo essere fiduciosi che la nostra risposta sia quella che ci aspettiamo.
Infine, analizziamo il corpo della risposta in un Array
e iteriamo su ogni film per registrare il suo nome e anno di uscita.
Dopo aver salvato e chiuso il file, esegui lo script con:
Otterrai il seguente output:
OutputCastle in the Sky, 1986
Grave of the Fireflies, 1988
My Neighbor Totoro, 1988
Kiki's Delivery Service, 1989
Only Yesterday, 1991
Porco Rosso, 1992
Pom Poko, 1994
Whisper of the Heart, 1995
Princess Mononoke, 1997
My Neighbors the Yamadas, 1999
Spirited Away, 2001
The Cat Returns, 2002
Howl's Moving Castle, 2004
Tales from Earthsea, 2006
Ponyo, 2008
Arrietty, 2010
From Up on Poppy Hill, 2011
The Wind Rises, 2013
The Tale of the Princess Kaguya, 2013
When Marnie Was There, 2014
Abbiamo ricevuto con successo un elenco dei film dello Studio Ghibli con l’anno di uscita. Ora completiamo questo programma scrivendo l’elenco dei film che stiamo attualmente registrando in un file.
Aggiorna il file callbackMovies.js
nel tuo editor di testo per includere il seguente codice evidenziato, che crea un file CSV con i nostri dati sui film:
Osservando i cambiamenti evidenziati, vediamo che importiamo il modulo fs
. Questo modulo è standard in tutte le installazioni di Node.js e contiene un metodo writeFile()
che può scrivere in modo asincrono su un file.
Invece di registrare i dati sulla console, ora li aggiungiamo a una variabile stringa movieList
. Successivamente, utilizziamo writeFile()
per salvare i contenuti di movieList
in un nuovo file—callbackMovies.csv
. Infine, forniamo una funzione di callback alla funzione writeFile()
, che ha un argomento: error
. Questo ci consente di gestire i casi in cui non siamo in grado di scrivere su un file, ad esempio quando l’utente su cui stiamo eseguendo il processo node
non ha tali autorizzazioni.
Salva il file e esegui nuovamente questo programma Node.js con:
Nella tua cartella ghibliMovies
, vedrai callbackMovies.csv
, che ha il seguente contenuto:
Castle in the Sky, 1986
Grave of the Fireflies, 1988
My Neighbor Totoro, 1988
Kiki's Delivery Service, 1989
Only Yesterday, 1991
Porco Rosso, 1992
Pom Poko, 1994
Whisper of the Heart, 1995
Princess Mononoke, 1997
My Neighbors the Yamadas, 1999
Spirited Away, 2001
The Cat Returns, 2002
Howl's Moving Castle, 2004
Tales from Earthsea, 2006
Ponyo, 2008
Arrietty, 2010
From Up on Poppy Hill, 2011
The Wind Rises, 2013
The Tale of the Princess Kaguya, 2013
When Marnie Was There, 2014
È importante notare che scriviamo nel nostro file CSV nella callback della richiesta HTTP. Una volta che il codice si trova nella funzione di callback, scriverà nel file solo dopo che la richiesta HTTP è stata completata. Se volessimo comunicare con un database dopo aver scritto il nostro file CSV, creeremmo un’altra funzione asincrona che verrebbe chiamata nella callback di writeFile()
. Più codice asincrono abbiamo, più funzioni di callback devono essere annidate.
Immaginiamo di voler eseguire cinque operazioni asincrone, ognuna in grado di essere eseguita solo quando un’altra è completa. Se dovessimo codificare questo, avremmo qualcosa del genere:
Quando le callback nidificate hanno molte linee di codice da eseguire, diventano sostanzialmente più complesse e illeggibili. Man mano che il tuo progetto JavaScript cresce in dimensioni e complessità, questo effetto diventerà sempre più evidente, fino a diventare alla fine incontrollabile. A causa di ciò, gli sviluppatori non utilizzano più le callback per gestire le operazioni asincrone. Per migliorare la sintassi del nostro codice asincrono, possiamo invece utilizzare le promesse.
Utilizzo delle Promesse per una Programmazione Asincrona Concisa
A promise is a JavaScript object that will return a value at some point in the future. Asynchronous functions can return promise objects instead of concrete values. If we get a value in the future, we say that the promise was fulfilled. If we get an error in the future, we say that the promise was rejected. Otherwise, the promise is still being worked on in a pending state.
Le promesse generalmente assumono la seguente forma:
Come mostrato in questo modello, le promesse utilizzano anche funzioni di callback. Abbiamo una funzione di callback per il metodo then()
, che viene eseguita quando una promessa viene soddisfatta. Abbiamo anche una funzione di callback per il metodo catch()
per gestire eventuali errori che si verificano durante l’esecuzione della promessa.
Diamo un’occhiata diretta alle promesse riscrivendo il nostro programma Studio Ghibli per utilizzare invece le promesse.
Axios è un client HTTP basato su promesse per JavaScript, quindi procediamo e installiamolo:
Ora, con il tuo editor di testo preferito, crea un nuovo file promiseMovies.js
:
Il nostro programma effettuerà una richiesta HTTP con axios
e quindi utilizzerà una versione speciale basata su promesse di fs
per salvare in un nuovo file CSV.
Scrivi questo codice in promiseMovies.js
così possiamo caricare Axios e inviare una richiesta HTTP all’API dei film:
Nella prima riga carichiamo il modulo axios
, memorizzando la funzione restituita in una costante chiamata axios
. Usiamo poi il metodo axios.get()
per inviare una richiesta HTTP all’API.
Il metodo axios.get()
restituisce una promessa. Concateniamo questa promessa per poter stampare l’elenco dei film di Ghibli sulla console:
Suddividiamo ciò che sta accadendo. Dopo aver effettuato una richiesta GET HTTP con axios.get()
, utilizziamo la funzione then()
, che viene eseguita solo quando la promessa viene soddisfatta. In questo caso, stampiamo i film sullo schermo come abbiamo fatto nell’esempio dei callback.
Per migliorare questo programma, aggiungi il codice evidenziato per scrivere i dati HTTP su un file:
Importiamo inoltre il modulo fs
ancora una volta. Nota come dopo l’importazione di fs
abbiamo .promises
. Node.js include una versione basata su promesse della libreria fs
basata su callback, quindi la compatibilità con le versioni precedenti non viene interrotta nei progetti legacy.
La prima funzione then()
che elabora la richiesta HTTP chiama ora fs.writeFile()
invece di stampare sulla console. Poiché abbiamo importato la versione basata su promesse di fs
, la nostra funzione writeFile()
restituisce un’altra promessa. Pertanto, aggiungiamo un’altra funzione then()
per quando la promessa di writeFile()
viene soddisfatta.
A promise can return a new promise, allowing us to execute promises one after the other. This paves the way for us to perform multiple asynchronous operations. This is called promise chaining, and it is analogous to nesting callbacks. The second then()
is only called after we successfully write to the file.
Nota: In questo esempio, non abbiamo controllato il codice di stato HTTP come abbiamo fatto nell’esempio di callback. Per default, axios
non adempie la sua promessa se riceve un codice di stato che indica un errore. Pertanto, non è più necessario convalidarlo.
Per completare questo programma, concatenare la promessa con una funzione catch()
come evidenziato nel seguente:
Se una qualsiasi promessa non viene adempiuta nella catena di promesse, JavaScript va automaticamente alla funzione catch()
se è stata definita. Ecco perché abbiamo solo una clausola catch()
anche se abbiamo due operazioni asincrone.
Confermiamo che il nostro programma produce lo stesso output eseguendo:
Nella tua cartella ghibliMovies
, vedrai il file promiseMovies.csv
contenente:
Castle in the Sky, 1986
Grave of the Fireflies, 1988
My Neighbor Totoro, 1988
Kiki's Delivery Service, 1989
Only Yesterday, 1991
Porco Rosso, 1992
Pom Poko, 1994
Whisper of the Heart, 1995
Princess Mononoke, 1997
My Neighbors the Yamadas, 1999
Spirited Away, 2001
The Cat Returns, 2002
Howl's Moving Castle, 2004
Tales from Earthsea, 2006
Ponyo, 2008
Arrietty, 2010
From Up on Poppy Hill, 2011
The Wind Rises, 2013
The Tale of the Princess Kaguya, 2013
When Marnie Was There, 2014
Con le promesse, possiamo scrivere codice molto più conciso rispetto all’uso solo di callback. La catena di promesse dei callback è un’opzione più pulita rispetto all’imbracatura dei callback. Tuttavia, man mano che effettuiamo più chiamate asincrone, la nostra catena di promesse diventa più lunga e più difficile da mantenere.
La verbosità dei callback e delle promesse deriva dalla necessità di creare funzioni quando abbiamo il risultato di un’attività asincrona. Un’esperienza migliore sarebbe attendere un risultato asincrono e metterlo in una variabile al di fuori della funzione. In questo modo, possiamo utilizzare i risultati nelle variabili senza dover creare una funzione. Possiamo ottenere questo con le parole chiave async
e await
.
Scrivere JavaScript con async
/await
Le parole chiave async
/await
forniscono una sintassi alternativa quando si lavora con le promesse. Invece di avere il risultato di una promessa disponibile nel metodo then()
, il risultato viene restituito come un valore come in qualsiasi altra funzione. Definiamo una funzione con la parola chiave async
per dire a JavaScript che si tratta di una funzione asincrona che restituisce una promessa. Utilizziamo la parola chiave await
per dire a JavaScript di restituire i risultati della promessa anziché restituire la promessa stessa quando viene soddisfatta.
In generale, l’utilizzo di async
/await
si presenta così:
Vediamo come l’utilizzo di async
/await
può migliorare il nostro programma Studio Ghibli. Utilizza il tuo editor di testo per creare e aprire un nuovo file asyncAwaitMovies.js
:
Nel tuo nuovo file JavaScript appena aperto, iniziamo importando gli stessi moduli che abbiamo usato nel nostro esempio di promessa:
Le importazioni sono le stesse di promiseMovies.js
perché async
/await
utilizza le promesse.
Ora utilizziamo la parola chiave async
per creare una funzione con il nostro codice asincrono:
Creiamo una nuova funzione chiamata saveMovies()
, ma includiamo async
all’inizio della sua definizione. Questo è importante poiché possiamo utilizzare solo la parola chiave await
in una funzione asincrona.
Utilizziamo la parola chiave await
per effettuare una richiesta HTTP che ottiene l’elenco dei film dall’API di Ghibli:
Nella nostra funzione saveMovies()
, effettuiamo una richiesta HTTP con axios.get()
come prima. Questa volta, non lo concateniamo con una funzione then()
. Invece, aggiungiamo await
prima che venga chiamato. Quando JavaScript vede await
, eseguirà solo il codice rimanente della funzione dopo che axios.get()
ha finito l’esecuzione e ha impostato la variabile response
. L’altro codice salva i dati del film in modo che possiamo scriverli su un file.
Scriviamo i dati del film su un file:
Utilizziamo anche la parola chiave await
quando scriviamo sul file con fs.writeFile()
.
Per completare questa funzione, dobbiamo gestire gli errori che le nostre promesse possono generare. Facciamolo racchiudendo il nostro codice in un blocco try
/catch
:
Dato che le promesse possono fallire, racchiudiamo il nostro codice asincrono con una clausola try
/catch
. Questo catturerà eventuali errori che vengono generati quando falliscono le operazioni di richiesta HTTP o di scrittura su file.
Infine, chiamiamo la nostra funzione asincrona saveMovies()
in modo che venga eseguita quando eseguiamo il programma con node
.
A prima vista, questo sembra essere un tipico blocco di codice JavaScript sincrono. Ha meno funzioni che vengono passate in giro, il che sembra un po’ più ordinato. Questi piccoli aggiustamenti rendono il codice asincrono con async
/await
più facile da mantenere.
Testa questa iterazione del nostro programma inserendo questo nel tuo terminale:
Nella tua cartella ghibliMovies
, verrà creato un nuovo file asyncAwaitMovies.csv
con i seguenti contenuti:
Castle in the Sky, 1986
Grave of the Fireflies, 1988
My Neighbor Totoro, 1988
Kiki's Delivery Service, 1989
Only Yesterday, 1991
Porco Rosso, 1992
Pom Poko, 1994
Whisper of the Heart, 1995
Princess Mononoke, 1997
My Neighbors the Yamadas, 1999
Spirited Away, 2001
The Cat Returns, 2002
Howl's Moving Castle, 2004
Tales from Earthsea, 2006
Ponyo, 2008
Arrietty, 2010
From Up on Poppy Hill, 2011
The Wind Rises, 2013
The Tale of the Princess Kaguya, 2013
When Marnie Was There, 2014
Hai ora utilizzato le funzionalità JavaScript async
/await
per gestire il codice asincrono.
Conclusione
In questo tutorial, hai imparato come JavaScript gestisce l’esecuzione delle funzioni e la gestione delle operazioni asincrone con il ciclo degli eventi. Hai quindi scritto programmi che hanno creato un file CSV dopo aver fatto una richiesta HTTP per i dati dei film usando varie tecniche di programmazione asincrona. Prima hai usato l’approccio basato su callback obsoleto. Poi hai usato le promesse e infine async
/await
per rendere la sintassi delle promesse più succinta.
Con la tua comprensione del codice asincrono con Node.js, ora puoi sviluppare programmi che traggono vantaggio dalla programmazione asincrona, come quelli che dipendono dalle chiamate API. Dai un’occhiata a questa lista di API pubbliche. Per utilizzarle, dovrai effettuare richieste HTTP asincrone come abbiamo fatto in questo tutorial. Per ulteriori studi, prova a costruire un’app che utilizzi queste API per praticare le tecniche che hai imparato qui.
Source:
https://www.digitalocean.com/community/tutorials/how-to-write-asynchronous-code-in-node-js