Come scrivere codice asincrono in Node.js

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

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:

function asynchronousFunction([ Function Arguments ], [ Callback Function ]) {
    [ Action ]
}

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:

  1. mkdir ghibliMovies

Quindi entra in quella cartella:

  1. cd ghibliMovies

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:Poi, installa la libreria request:

  1. npm init -y

Quindi, installa la libreria request:

  1. npm i request --save

Ora apri un nuovo file chiamato callbackMovies.js in un editor di testo come nano:

  1. nano callbackMovies.js

Nel tuo editor di testo, inserisci il seguente codice. Iniziamo inviando una richiesta HTTP con il modulo request:

callbackMovies.js
const request = require('request');

request('https://ghibliapi.herokuapp.com/films');

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:

callbackMovies.js
const request = require('request');

request('https://ghibliapi.herokuapp.com/films', (error, response, body) => {
    if (error) {
        console.error(`Could not send request to API: ${error.message}`);
        return;
    }

    if (response.statusCode != 200) {
        console.error(`Expected status code 200 but received ${response.statusCode}.`);
        return;
    }

    console.log('Processing our list of movies');
    movies = JSON.parse(body);
    movies.forEach(movie => {
        console.log(`${movie['title']}, ${movie['release_date']}`);
    });
});

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:

  1. node callbackMovies.js

Otterrai il seguente output:

Output
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

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:

callbackMovies.js
const request = require('request');
const fs = require('fs');

request('https://ghibliapi.herokuapp.com/films', (error, response, body) => {
    if (error) {
        console.error(`Could not send request to API: ${error.message}`);
        return;
    }

    if (response.statusCode != 200) {
        console.error(`Expected status code 200 but received ${response.statusCode}.`);
        return;
    }

    console.log('Processing our list of movies');
    movies = JSON.parse(body);
    let movieList = '';
    movies.forEach(movie => {
        movieList += `${movie['title']}, ${movie['release_date']}\n`;
    });

    fs.writeFile('callbackMovies.csv', movieList, (error) => {
        if (error) {
            console.error(`Could not save the Ghibli movies to a file: ${error}`);
            return;
        }

        console.log('Saved our list of movies to callbackMovies.csv');;
    });
});

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:

  1. node callbackMovies.js

Nella tua cartella ghibliMovies, vedrai callbackMovies.csv, che ha il seguente contenuto:

callbackMovies.csv
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:

doSomething1(() => {
    doSomething2(() => {
        doSomething3(() => {
            doSomething4(() => {
                doSomething5(() => {
                    // azione finale
                });
            });
        }); 
    });
});

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:

promiseFunction()
    .then([ Callback Function for Fulfilled Promise ])
    .catch([ Callback Function for Rejected Promise ])

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:

  1. npm i axios --save

Ora, con il tuo editor di testo preferito, crea un nuovo file promiseMovies.js:

  1. nano 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:

promiseMovies.js
const axios = require('axios');

axios.get('https://ghibliapi.herokuapp.com/films');

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:

promiseMovies.js
const axios = require('axios');
const fs = require('fs').promises;


axios.get('https://ghibliapi.herokuapp.com/films')
    .then((response) => {
        console.log('Successfully retrieved our list of movies');
        response.data.forEach(movie => {
            console.log(`${movie['title']}, ${movie['release_date']}`);
        });
    })

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:

promiseMovies.js
const axios = require('axios');
const fs = require('fs').promises;


axios.get('https://ghibliapi.herokuapp.com/films')
    .then((response) => {
        console.log('Successfully retrieved our list of movies');
        let movieList = '';
        response.data.forEach(movie => {
            movieList += `${movie['title']}, ${movie['release_date']}\n`;
        });

        return fs.writeFile('promiseMovies.csv', movieList);
    })
    .then(() => {
        console.log('Saved our list of movies to promiseMovies.csv');
    })

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:

promiseMovies.js
const axios = require('axios');
const fs = require('fs').promises;


axios.get('https://ghibliapi.herokuapp.com/films')
    .then((response) => {
        console.log('Successfully retrieved our list of movies');
        let movieList = '';
        response.data.forEach(movie => {
            movieList += `${movie['title']}, ${movie['release_date']}\n`;
        });

        return fs.writeFile('promiseMovies.csv', movieList);
    })
    .then(() => {
        console.log('Saved our list of movies to promiseMovies.csv');
    })
    .catch((error) => {
        console.error(`Could not save the Ghibli movies to a file: ${error}`);
    });

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:

  1. node promiseMovies.js

Nella tua cartella ghibliMovies, vedrai il file promiseMovies.csv contenente:

promiseMovies.csv
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ì:

async function() {
    await [Asynchronous Action]
}

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:

  1. nano asyncAwaitMovies.js

Nel tuo nuovo file JavaScript appena aperto, iniziamo importando gli stessi moduli che abbiamo usato nel nostro esempio di promessa:

asyncAwaitMovies.js
const axios = require('axios');
const fs = require('fs').promises;

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:

asyncAwaitMovies.js
const axios = require('axios');
const fs = require('fs').promises;

async function saveMovies() {}

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:

asyncAwaitMovies.js
const axios = require('axios');
const fs = require('fs').promises;

async function saveMovies() {
    let response = await axios.get('https://ghibliapi.herokuapp.com/films');
    let movieList = '';
    response.data.forEach(movie => {
        movieList += `${movie['title']}, ${movie['release_date']}\n`;
    });
}

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:

asyncAwaitMovies.js
const axios = require('axios');
const fs = require('fs').promises;

async function saveMovies() {
    let response = await axios.get('https://ghibliapi.herokuapp.com/films');
    let movieList = '';
    response.data.forEach(movie => {
        movieList += `${movie['title']}, ${movie['release_date']}\n`;
    });
    await fs.writeFile('asyncAwaitMovies.csv', movieList);
}

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:

asyncAwaitMovies.js
const axios = require('axios');
const fs = require('fs').promises;

async function saveMovies() {
    try {
        let response = await axios.get('https://ghibliapi.herokuapp.com/films');
        let movieList = '';
        response.data.forEach(movie => {
            movieList += `${movie['title']}, ${movie['release_date']}\n`;
        });
        await fs.writeFile('asyncAwaitMovies.csv', movieList);
    } catch (error) {
        console.error(`Could not save the Ghibli movies to a file: ${error}`);
    }
}

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.

asyncAwaitMovies.js
const axios = require('axios');
const fs = require('fs').promises;

async function saveMovies() {
    try {
        let response = await axios.get('https://ghibliapi.herokuapp.com/films');
        let movieList = '';
        response.data.forEach(movie => {
            movieList += `${movie['title']}, ${movie['release_date']}\n`;
        });
        await fs.writeFile('asyncAwaitMovies.csv', movieList);
    } catch (error) {
        console.error(`Could not save the Ghibli movies to a file: ${error}`);
    }
}

saveMovies();

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:

  1. node asyncAwaitMovies.js

Nella tua cartella ghibliMovies, verrà creato un nuovo file asyncAwaitMovies.csv con i seguenti contenuti:

asyncAwaitMovies.csv
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