Wie man asynchronen Code in Node.js schreibt

Der Autor hat den Open Internet/Free Speech Fund ausgewählt, um eine Spende im Rahmen des Write for Donations-Programms zu erhalten.

Einführung

Für viele Programme in JavaScript wird der Code ausgeführt, während der Entwickler ihn schreibt – Zeile für Zeile. Dies wird als synchrone Ausführung bezeichnet, da die Zeilen nacheinander, in der Reihenfolge, in der sie geschrieben wurden, ausgeführt werden. Nicht jede Anweisung, die Sie dem Computer geben, muss jedoch sofort bearbeitet werden. Wenn Sie beispielsweise eine Netzwerkanfrage senden, muss der Prozess, der Ihren Code ausführt, auf die Rückkehr der Daten warten, bevor er damit arbeiten kann. In diesem Fall würde Zeit vergeudet, wenn er nicht anderen Code ausführen würde, während er auf den Abschluss der Netzwerkanfrage wartet. Um dieses Problem zu lösen, verwenden Entwickler asynchrone Programmierung, bei der Zeilen des Codes in einer anderen Reihenfolge ausgeführt werden als die, in der sie geschrieben wurden. Mit asynchroner Programmierung können wir anderen Code ausführen, während wir auf lange Aktivitäten wie Netzwerkanfragen warten.

JavaScript-Code wird in einem einzigen Thread innerhalb eines Computerprozesses ausgeführt. Sein Code wird synchron auf diesem Thread verarbeitet, wobei jeweils nur eine Anweisung ausgeführt wird. Wenn wir also eine langlaufende Aufgabe auf diesem Thread ausführen würden, wird der gesamte verbleibende Code blockiert, bis die Aufgabe abgeschlossen ist. Durch die Nutzung der asynchronen Programmierfunktionen von JavaScript können wir langlaufende Aufgaben auf einen Hintergrundthread auslagern, um dieses Problem zu vermeiden. Wenn die Aufgabe abgeschlossen ist, wird der Code, den wir benötigen, um die Daten der Aufgabe zu verarbeiten, wieder auf den Hauptthread gesetzt.

In diesem Tutorial erfahren Sie, wie JavaScript asynchrone Aufgaben mithilfe der Ereignisschleife verwaltet, die eine JavaScript-Konstruktion ist, die eine neue Aufgabe abschließt, während sie auf eine andere wartet. Anschließend erstellen Sie ein Programm, das asynchrone Programmierung verwendet, um eine Liste von Filmen von einer Studio Ghibli API anfordert und die Daten in einer CSV-Datei speichert. Der asynchrone Code wird auf drei Arten geschrieben: Callbacks, Promises und mit den async/await Schlüsselwörtern.

Hinweis: Zum Zeitpunkt dieses Schreibens wird asynchrone Programmierung nicht mehr nur mit Callbacks durchgeführt, aber das Erlernen dieser veralteten Methode kann einen großen Kontext darüber liefern, warum die JavaScript-Community jetzt Promises verwendet. Die async/await Schlüsselwörter ermöglichen es uns, Promises auf eine weniger umständliche Weise zu verwenden, und sind somit die Standardmethode zur asynchronen Programmierung in JavaScript zum Zeitpunkt dieses Artikels.

Voraussetzungen

Der Event-Loop

Beginnen wir mit der Untersuchung der internen Funktionsweise der JavaScript-Funktionsausführung. Die Kenntnis der Art und Weise, wie dies funktioniert, ermöglicht es Ihnen, asynchrone Codeabschnitte gezielter zu schreiben und hilft Ihnen zukünftig beim Fehlersuchen in Ihrem Code.

Während der JavaScript-Interpreter den Code ausführt, wird jede aufgerufene Funktion dem Aufrufstack von JavaScript hinzugefügt. Der Aufrufstack ist eine Stack-Datenstruktur – eine Listenähnliche Datenstruktur, bei der Elemente nur oben hinzugefügt und von dort entfernt werden können. Stacks folgen dem Prinzip „Last in, first out“ oder LIFO. Wenn Sie zwei Elemente auf dem Stack hinzufügen, wird das zuletzt hinzugefügte Element zuerst entfernt.

Lassen Sie uns das mit einem Beispiel am Aufrufstack veranschaulichen. Wenn JavaScript eine Funktion functionA() findet, wird sie dem Aufrufstack hinzugefügt. Wenn diese Funktion functionA() eine weitere Funktion functionB() aufruft, wird functionB() oben auf dem Aufrufstack hinzugefügt. Wenn JavaScript die Ausführung einer Funktion abgeschlossen hat, wird sie vom Aufrufstack entfernt. Daher wird JavaScript functionB() zuerst ausführen, sie vom Stack entfernen, wenn sie fertig ist, und dann die Ausführung von functionA() beenden und sie vom Aufrufstack entfernen. Deshalb werden innere Funktionen immer vor ihren äußeren Funktionen ausgeführt.

Wenn JavaScript auf eine asynchrone Operation wie das Schreiben in eine Datei stößt, fügt es sie einer Tabelle in seinem Speicher hinzu. Diese Tabelle speichert die Operation, die Bedingung für ihre Beendigung und die Funktion, die aufgerufen werden soll, wenn sie abgeschlossen ist. Wenn die Operation abgeschlossen ist, fügt JavaScript die zugehörige Funktion der Warteschlange für Nachrichten hinzu. Eine Warteschlange ist eine weitere listenähnliche Datenstruktur, in der Elemente nur am Ende hinzugefügt, aber am Anfang entfernt werden können. In der Warteschlange, wenn zwei oder mehr asynchrone Operationen bereit sind, ihre Funktionen auszuführen, wird die asynchrone Operation, die zuerst abgeschlossen wurde, zuerst markiert.

Funktionen in der Nachrichtenwarteschlange warten darauf, zum Aufrufstapel hinzugefügt zu werden. Die Ereignisschleife ist ein fortlaufender Prozess, der überprüft, ob der Aufrufstapel leer ist. Ist er leer, wird das erste Element in der Nachrichtenwarteschlange in den Aufrufstapel verschoben. JavaScript priorisiert Funktionen in der Nachrichtenwarteschlange über Funktionsaufrufe, die es im Code interpretiert. Die kombinierte Wirkung des Aufrufstapels, der Nachrichtenwarteschlange und der Ereignisschleife ermöglicht es JavaScript-Code, verarbeitet zu werden, während asynchrone Aktivitäten verwaltet werden.

Jetzt, da Sie ein grundlegendes Verständnis der Ereignisschleife haben, wissen Sie, wie der asynchrone Code, den Sie schreiben, ausgeführt wird. Mit diesem Wissen können Sie jetzt asynchronen Code mit drei verschiedenen Ansätzen erstellen: Callbacks, Promises und async/await.

Asynchrones Programmieren mit Callbacks

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.

Für lange Zeit waren Rückrufe der häufigste Mechanismus zum Schreiben von asynchronem Code, aber jetzt sind sie größtenteils veraltet, weil sie den Code verwirrend lesen lassen können. In diesem Schritt schreiben Sie ein Beispiel für asynchronen Code mit Rückrufen, damit Sie ihn als Ausgangspunkt verwenden können, um die gesteigerte Effizienz anderer Strategien zu sehen.

Es gibt viele Möglichkeiten, Rückruffunktionen in einer anderen Funktion zu verwenden. Im Allgemeinen haben sie diese Struktur:

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

Obwohl es von JavaScript oder Node.js syntaktisch nicht erforderlich ist, die Rückruffunktion als letzten Argument der äußeren Funktion zu haben, ist es eine gängige Praxis, die Rückrufe leichter identifizierbar macht. Es ist auch üblich, dass JavaScript-Entwickler eine anonyme Funktion als Rückruf verwenden. Anonyme Funktionen sind solche, die ohne Namen erstellt werden. Es ist normalerweise viel lesbarer, wenn eine Funktion am Ende der Argumentenliste definiert ist.

Um Rückrufe zu demonstrieren, erstellen wir ein Node.js-Modul, das eine Liste von Filmen des Studio Ghibli in eine Datei schreibt. Erstellen Sie zunächst einen Ordner, der unsere JavaScript-Datei und ihre Ausgabe speichert:

  1. mkdir ghibliMovies

Dann betreten Sie diesen Ordner:

  1. cd ghibliMovies

Wir beginnen damit, eine HTTP-Anfrage an die Studio Ghibli API zu stellen, deren Rückruffunktion die Ergebnisse protokolliert. Dazu werden wir eine Bibliothek installieren, die es uns ermöglicht, die Daten einer HTTP-Antwort in einem Rückruf abzurufen.

Initialisieren Sie in Ihrem Terminal npm, damit wir später Referenzen für unsere Pakete haben:Dann installieren Sie die request-Bibliothek:

  1. npm init -y

Dann installieren Sie die request-Bibliothek:

  1. npm i request --save

Öffnen Sie nun eine neue Datei namens callbackMovies.js in einem Texteditor wie nano:

  1. nano callbackMovies.js

Geben Sie in Ihrem Texteditor den folgenden Code ein. Beginnen wir mit dem Senden einer HTTP-Anfrage mit dem request-Modul:

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

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

In der ersten Zeile laden wir das request-Modul, das über npm installiert wurde. Das Modul gibt eine Funktion zurück, die HTTP-Anfragen stellen kann; wir speichern diese Funktion dann im request-Konstanten.

Wir senden dann die HTTP-Anfrage mit der request()-Funktion. Lassen Sie uns nun die Daten aus der HTTP-Anfrage in der Konsole ausgeben, indem wir die hervorgehobenen Änderungen hinzufügen:

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']}`);
    });
});

Wenn wir die request()-Funktion verwenden, geben wir ihr zwei Parameter:

  • Die URL der Website, die wir anfragen möchten
  • A callback function that handles any errors or successful responses after the request is complete

Unsere Callback-Funktion hat drei Argumente: error, response und body. Wenn die HTTP-Anfrage abgeschlossen ist, werden den Argumenten automatisch Werte entsprechend dem Ergebnis zugewiesen. Wenn das Senden der Anfrage fehlgeschlagen ist, würde error ein Objekt enthalten, aber response und body wären null. Wenn es erfolgreich die Anfrage gemacht hat, wird die HTTP-Antwort in response gespeichert. Wenn unsere HTTP-Antwort Daten zurückgibt (in diesem Beispiel erhalten wir JSON), werden die Daten in body gesetzt.

Unsere Rückruffunktion überprüft zuerst, ob ein Fehler aufgetreten ist. Es ist bewährte Praxis, zunächst auf Fehler in einem Rückruf zu überprüfen, damit die Ausführung des Rückrufs nicht mit fehlenden Daten fortgesetzt wird. In diesem Fall protokollieren wir den Fehler und die Ausführung der Funktion. Anschließend überprüfen wir den Statuscode der Antwort. Unser Server ist möglicherweise nicht immer verfügbar, und APIs können sich ändern, was dazu führen kann, dass einmal sinnvolle Anfragen inkorrekt werden. Durch Überprüfen, ob der Statuscode 200 ist, was bedeutet, dass die Anfrage „OK“ war, können wir darauf vertrauen, dass unsere Antwort das ist, was wir erwarten.

Zum Schluss analysieren wir den Antworttext zu einem Array und durchlaufen jeden Film, um seinen Namen und sein Veröffentlichungsjahr zu protokollieren.

Nachdem Sie die Datei gespeichert und geschlossen haben, führen Sie dieses Skript mit folgendem Befehl aus:

  1. node callbackMovies.js

Sie erhalten die folgende Ausgabe:

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

Wir haben erfolgreich eine Liste von Studio Ghibli-Filmen mit dem Jahr erhalten, in dem sie veröffentlicht wurden. Jetzt vervollständigen wir dieses Programm, indem wir die aktuell protokollierte Filmliste in eine Datei schreiben.

Aktualisieren Sie die Datei callbackMovies.js in Ihrem Texteditor, um den folgenden hervorgehobenen Code einzuschließen, der eine CSV-Datei mit unseren Filmdaten erstellt:

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');;
    });
});

Unter Berücksichtigung der hervorgehobenen Änderungen sehen wir, dass wir das Modul fs importieren. Dieses Modul ist in allen Node.js-Installationen standardmäßig enthalten und enthält eine Methode writeFile(), die asynchron in eine Datei schreiben kann.

Anstelle die Daten in der Konsole zu protokollieren, fügen wir sie jetzt einer Zeichenfolgenvariable movieList hinzu. Anschließend verwenden wir writeFile(), um den Inhalt von movieList in eine neue Datei namens callbackMovies.csv zu speichern. Schließlich übergeben wir eine Rückruffunktion an die writeFile()-Funktion, die ein Argument hat: error. Dies ermöglicht es uns, Fälle zu behandeln, in denen wir nicht in der Lage sind, in eine Datei zu schreiben, z. B. wenn der Benutzer, auf dem wir den node-Prozess ausführen, nicht über die entsprechenden Berechtigungen verfügt.

Speichern Sie die Datei und führen Sie dieses Node.js-Programm erneut aus mit:

  1. node callbackMovies.js

In Ihrem ghibliMovies-Ordner sehen Sie callbackMovies.csv, das folgenden Inhalt hat:

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

Es ist wichtig zu beachten, dass wir in unserer CSV-Datei im Rückruf der HTTP-Anfrage schreiben. Sobald der Code in der Rückruffunktion ist, wird er erst nach Abschluss der HTTP-Anfrage in die Datei schreiben. Wenn wir nach dem Schreiben unserer CSV-Datei mit einer Datenbank kommunizieren wollten, würden wir eine weitere asynchrone Funktion erstellen, die im Rückruf von writeFile() aufgerufen würde. Je mehr asynchroner Code wir haben, desto mehr Rückruffunktionen müssen verschachtelt werden.

Stellen wir uns vor, dass wir fünf asynchrone Operationen ausführen möchten, von denen jede nur ausgeführt werden kann, wenn eine andere abgeschlossen ist. Wenn wir dies codieren würden, hätten wir so etwas:

doSomething1(() => {
    doSomething2(() => {
        doSomething3(() => {
            doSomething4(() => {
                doSomething5(() => {
                    // endgültige Aktion
                });
            });
        }); 
    });
});

Wenn verschachtelte Rückrufe viele Codezeilen ausführen müssen, werden sie wesentlich komplexer und unleserlicher. Wenn Ihr JavaScript-Projekt an Größe und Komplexität zunimmt, wird dieser Effekt immer ausgeprägter, bis er letztendlich nicht mehr beherrschbar ist. Aus diesem Grund verwenden Entwickler keine Rückrufe mehr, um asynchrone Operationen zu behandeln. Um die Syntax unseres asynchronen Codes zu verbessern, können wir stattdessen Versprechen verwenden.

Die Verwendung von Versprechen für präzise asynchrone Programmierung

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.

Versprechen haben im Allgemeinen folgende Form:

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

Wie in dieser Vorlage gezeigt, verwenden Versprechen auch Rückruffunktionen. Wir haben eine Rückruffunktion für die Methode then(), die ausgeführt wird, wenn ein Versprechen erfüllt ist. Wir haben auch eine Rückruffunktion für die Methode catch(), um Fehler zu behandeln, die während der Ausführung des Versprechens auftreten.

Lassen Sie uns aus erster Hand Erfahrungen mit Versprechen machen, indem wir unser Studio-Ghibli-Programm neu schreiben, um anstelle davon Versprechen zu verwenden.

Axios ist ein auf Versprechen basierender HTTP-Client für JavaScript, also lassen Sie uns damit fortfahren und ihn installieren:

  1. npm i axios --save

Jetzt, mit Ihrem Texteditor Ihrer Wahl, erstellen Sie eine neue Datei promiseMovies.js:

  1. nano promiseMovies.js

Unser Programm wird eine HTTP-Anfrage mit axios machen und dann eine spezielle, auf Versprechen basierende Version von fs verwenden, um eine neue CSV-Datei zu speichern.

Geben Sie diesen Code in promiseMovies.js ein, damit wir Axios laden und eine HTTP-Anfrage an die Film-API senden können:

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

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

In der ersten Zeile laden wir das Modul axios und speichern die zurückgegebene Funktion in einer Konstanten namens axios. Wir verwenden dann die Methode axios.get(), um eine HTTP-Anfrage an die API zu senden.

Die Methode axios.get() gibt ein Versprechen zurück. Lassen Sie uns dieses Versprechen verketten, damit wir die Liste der Ghibli-Filme auf der Konsole ausgeben können:

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']}`);
        });
    })

Lassen Sie uns analysieren, was passiert. Nachdem wir eine HTTP-GET-Anfrage mit axios.get() gestellt haben, verwenden wir die Funktion then(), die nur ausgeführt wird, wenn das Versprechen erfüllt ist. In diesem Fall geben wir die Filme auf dem Bildschirm aus, wie wir es im Callback-Beispiel getan haben.

Um dieses Programm zu verbessern, fügen Sie den markierten Code hinzu, um die HTTP-Daten in eine Datei zu schreiben:

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');
    })

Wir importieren zusätzlich das Modul fs erneut. Beachten Sie, wie nach dem fs-Import .promises steht. Node.js enthält eine auf Versprechen basierende Version der callback-basierten fs-Bibliothek, sodass die Abwärtskompatibilität in Legacy-Projekten nicht gebrochen wird.

Die erste then()-Funktion, die die HTTP-Anfrage verarbeitet, ruft nun fs.writeFile() anstelle des Ausdrucks auf der Konsole auf. Da wir die auf Versprechen basierende Version von fs importiert haben, gibt unsere writeFile()-Funktion ein weiteres Versprechen zurück. Daher fügen wir eine weitere then()-Funktion hinzu, wenn das Versprechen von writeFile() erfüllt ist.

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.

Hinweis: In diesem Beispiel haben wir nicht nach dem HTTP-Statuscode überprüft, wie wir es im Rückrufbeispiel getan haben. Standardmäßig erfüllt axios sein Versprechen nicht, wenn es einen Statuscode erhält, der auf einen Fehler hinweist. Daher müssen wir es nicht mehr validieren.

Um dieses Programm abzuschließen, verketten Sie das Versprechen mit einer catch()-Funktion, wie im folgenden hervorgehoben:

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}`);
    });

Wenn ein Versprechen in der Versprechenkette nicht erfüllt wird, geht JavaScript automatisch zur catch()-Funktion, wenn sie definiert wurde. Deshalb haben wir nur eine catch()-Klausel, obwohl wir zwei asynchrone Operationen haben.

Lassen Sie uns bestätigen, dass unser Programm dieselbe Ausgabe erzeugt, indem Sie ausführen:

  1. node promiseMovies.js

In Ihrem ghibliMovies-Ordner sehen Sie die Datei promiseMovies.csv mit folgendem Inhalt:

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

Mit Versprechen können wir viel prägnanteren Code schreiben als nur mit Rückrufen. Die Versprechenkette von Rückrufen ist eine sauberere Option als das Verschachteln von Rückrufen. Wenn wir jedoch mehr asynchrone Aufrufe tätigen, wird unsere Versprechenkette länger und schwieriger zu pflegen.

Die Redundanz von Rückrufen und Versprechen resultiert aus der Notwendigkeit, Funktionen zu erstellen, wenn wir das Ergebnis einer asynchronen Aufgabe haben. Eine bessere Erfahrung wäre es, auf ein asynchrones Ergebnis zu warten und es in einer Variablen außerhalb der Funktion zu platzieren. Auf diese Weise können wir die Ergebnisse in den Variablen verwenden, ohne eine Funktion erstellen zu müssen. Dies können wir mit den Schlüsselwörtern async und await erreichen.

Schreiben von JavaScript mit async/await

Die Schlüsselwörter async/await bieten eine alternative Syntax beim Arbeiten mit Promises. Anstatt das Ergebnis einer Promise-Methode then() verfügbar zu haben, wird das Ergebnis wie bei jeder anderen Funktion als Wert zurückgegeben. Wir definieren eine Funktion mit dem Schlüsselwort async, um JavaScript mitzuteilen, dass es sich um eine asynchrone Funktion handelt, die eine Promise zurückgibt. Wir verwenden das Schlüsselwort await, um JavaScript mitzuteilen, die Ergebnisse der Promise zurückzugeben, anstatt die Promise selbst zurückzugeben, wenn sie erfüllt ist.

Allgemein sieht die Verwendung von async/await so aus:

async function() {
    await [Asynchronous Action]
}

Sehen wir uns an, wie die Verwendung von async/await unser Studio-Ghibli-Programm verbessern kann. Verwenden Sie Ihren Texteditor, um eine neue Datei asyncAwaitMovies.js zu erstellen und zu öffnen:

  1. nano asyncAwaitMovies.js

In Ihrer neu geöffneten JavaScript-Datei beginnen wir damit, dieselben Module zu importieren, die wir in unserem Promise-Beispiel verwendet haben:

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

Die Imports sind die gleichen wie in promiseMovies.js, da async/await Promises verwendet.

Jetzt verwenden wir das Schlüsselwort async, um eine Funktion mit unserem asynchronen Code zu erstellen:

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

async function saveMovies() {}

Wir erstellen eine neue Funktion namens saveMovies(), aber wir fügen async am Anfang ihrer Definition hinzu. Dies ist wichtig, da wir das await-Schlüsselwort nur in einer asynchronen Funktion verwenden können.

Verwenden Sie das await-Schlüsselwort, um eine HTTP-Anfrage abzusetzen, die die Liste der Filme von der Ghibli-API abruft:

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`;
    });
}

In unserer saveMovies()-Funktion stellen wir eine HTTP-Anfrage mit axios.get() wie zuvor. Diesmal ketten wir sie nicht mit einer then()-Funktion. Stattdessen fügen wir await davor ein. Wenn JavaScript await sieht, wird es nur den verbleibenden Code der Funktion ausführen, nachdem axios.get() die Ausführung beendet und die response-Variable gesetzt hat. Der andere Code speichert die Film-Daten, damit wir sie in eine Datei schreiben können.

Lassen Sie uns die Film-Daten in eine Datei schreiben:

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);
}

Wir verwenden auch das await-Schlüsselwort, wenn wir mit fs.writeFile() in die Datei schreiben.

Um diese Funktion abzuschließen, müssen wir Fehler abfangen, die unsere Versprechen werfen können. Lassen Sie uns dies tun, indem wir unseren Code in einen try/catch-Block einschließen:

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}`);
    }
}

Da Versprechen scheitern können, umgeben wir unseren asynchronen Code mit einer try/catch-Klausel. Dadurch werden Fehler erfasst, die auftreten, wenn die HTTP-Anfrage oder die Dateischreibvorgänge fehlschlagen.

Zum Abschluss rufen wir unsere asynchrone Funktion saveMovies() auf, damit sie ausgeführt wird, wenn wir das Programm mit node ausführen.

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();

Auf den ersten Blick sieht dies wie ein typischer synchroner JavaScript-Codeblock aus. Es werden weniger Funktionen übergeben, was etwas ordentlicher aussieht. Diese kleinen Anpassungen erleichtern die Wartung des asynchronen Codes mit async/await. Testen Sie diese Version unseres Programms, indem Sie Folgendes in Ihrem Terminal eingeben:

In Ihrem ghibliMovies-Ordner wird eine neue asyncAwaitMovies.csv-Datei mit folgendem Inhalt erstellt:

  1. node asyncAwaitMovies.js

Sie haben nun die JavaScript-Funktionen async/await verwendet, um asynchronen Code zu verwalten.

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

Schlussfolgerung

In diesem Tutorial haben Sie gelernt, wie JavaScript Funktionen ausführt und asynchrone Operationen mit der Ereignisschleife verwaltet. Anschließend haben Sie Programme geschrieben, die nach einem HTTP-Anforderung für Filmdaten eine CSV-Datei erstellt haben, indem Sie verschiedene asynchrone Programmierungstechniken verwendet haben. Zuerst haben Sie den veralteten rückrufbasierten Ansatz verwendet. Dann haben Sie Promises verwendet und schließlich async/await, um die Syntax von Promises prägnanter zu gestalten.

Mit deinem Verständnis für asynchronen Code mit Node.js kannst du nun Programme entwickeln, die von asynchronem Programmieren profitieren, wie solche, die auf API-Aufrufen basieren. Werfe einen Blick auf diese Liste von öffentlichen APIs. Um sie zu verwenden, musst du asynchrone HTTP-Anfragen stellen, wie wir es in diesem Tutorial gemacht haben. Für weiterführende Studien versuche, eine App zu entwickeln, die diese APIs verwendet, um die hier erlernten Techniken zu üben.

Source:
https://www.digitalocean.com/community/tutorials/how-to-write-asynchronous-code-in-node-js