ES2017 Asynchrone Funktionen verwenden

Avatar of Eric Windmill
Eric Windmill am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

ES2017 wurde im Juni finalisiert, und mit ihm kam breite Unterstützung für mein neues Lieblingsfeature von JavaScript: async Funktionen! Wenn Sie jemals mit der Argumentation über asynchrones JavaScript zu kämpfen hatten, ist das hier genau das Richtige für Sie. Wenn nicht, dann sind Sie wahrscheinlich ein Supergenie.

Async-Funktionen erlauben es Ihnen mehr oder weniger, sequenziellen JavaScript-Code zu schreiben, ohne Ihre gesamte Logik in Callbacks, Generatoren oder Promises zu verpacken. Betrachten Sie dies

function logger() {
    let data = fetch('http://sampleapi.com/posts')
    console.log(data)
}

logger()

Dieser Code tut nicht, was Sie erwarten. Wenn Sie jemals etwas in JS erstellt haben, wissen Sie wahrscheinlich warum.

Aber dieser Code tut, was Sie erwarten würden.

async function logger() {
    let data = await fetch('http://sampleapi.com/posts')
    console.log(data)
}

logger()

Dieser intuitive (und hübsche) Code funktioniert und benötigt nur zwei zusätzliche Wörter!

Asynchrones JavaScript vor ES6

Bevor wir uns mit async und await beschäftigen, ist es wichtig, dass Sie Promises verstehen. Und um Promises zu schätzen, müssen wir einen Schritt zurückgehen zu einfachen Callbacks.

Promises wurden in ES6 eingeführt und brachten große Verbesserungen beim Schreiben von asynchronem Code in JavaScript. Kein "Callback Hell" mehr, wie es manchmal liebevoll genannt wird.

Ein Callback ist eine Funktion, die an eine andere Funktion übergeben und innerhalb dieser Funktion als Reaktion auf ein beliebiges Ereignis aufgerufen werden kann. Es ist fundamental für JS.

function readFile('file.txt', (data) => {
  // This is inside the callback function
  console.log(data)
}

Diese Funktion protokolliert einfach die Daten aus einer Datei, was erst möglich ist, wenn das Lesen der Datei abgeschlossen ist. Es scheint einfach, aber was, wenn Sie fünf verschiedene Dateien *sequenziell* lesen und protokollieren wollten?

Vor Promises müssten Sie Callbacks verschachteln, um sequentielle Aufgaben auszuführen, wie folgt

// This is officially callback hell
function combineFiles(file1, file2, file3, printFileCallBack) {
    let newFileText = ''
    readFile(string1, (text) => {
        newFileText += text
        readFile(string2, (text) => {
            newFileText += text
            readFile(string3, (text) => {
                newFileText += text
                printFileCallBack(newFileText)
            }
        }
    } 
}

Es ist schwer zu verstehen und schwer zu verfolgen. Dies schließt noch nicht einmal die Fehlerbehandlung für das durchaus mögliche Szenario ein, dass eine der Dateien nicht existiert.

Ich verspreche, es wird besser (verstehen Sie?!)

Hier kann ein Promise helfen. Ein Promise ist eine Möglichkeit, über Daten zu argumentieren, die noch nicht existieren, von denen Sie aber wissen, dass sie es tun werden. Kyle Simpson, Autor der You Don’t Know JS-Reihe, ist bekannt für seine Vorträge über asynchrones JavaScript. Seine Erklärung von Promises aus diesem Vortrag ist genau richtig: Es ist wie Essen in einem Fast-Food-Restaurant bestellen.

  1. Bestellen Sie Ihr Essen.
  2. Bezahlen Sie für Ihr Essen und erhalten Sie ein Ticket mit einer Bestellnummer.
  3. Warten Sie auf Ihr Essen.
  4. Wenn Ihr Essen fertig ist, rufen sie Ihre Ticketnummer auf.
  5. Erhalten Sie das Essen.

Wie er hervorhebt, können Sie Ihr Essen vielleicht nicht essen, während Sie darauf warten, aber Sie können darüber nachdenken und sich darauf vorbereiten. Sie können mit Ihrem Tag fortfahren und wissen, dass das Essen kommen wird, auch wenn Sie es noch nicht haben, weil Ihnen das Essen "versprochen" wurde. Das ist alles, was ein Promise ist. Ein Objekt, das Daten repräsentiert, die irgendwann existieren werden.

readFile(file1)
  .then((file1-data) => { /* do something */ })
  .then((previous-promise-data) => { /* do the next thing */ })
  .catch( /* handle errors */ )

Das ist die promise-Syntax. Ihr Hauptvorteil besteht darin, dass sie eine intuitive Möglichkeit bietet, sequentielle Ereignisse zu verketten. Dieses einfache Beispiel ist in Ordnung, aber Sie sehen, dass wir immer noch Callbacks verwenden. Promises sind nur dünne Wrapper um Callbacks, die es etwas intuitiver machen.

Der (neue) beste Weg: Async / Await

Vor ein paar Jahren hielten async-Funktionen Einzug in das JavaScript-Ökosystem. Seit letztem Monat sind sie ein offizielles Feature der Sprache und weitgehend unterstützt.

Die Schlüsselwörter async und await sind ein dünner Wrapper, der auf Promises und Generatoren aufbaut. Im Wesentlichen erlaubt er uns, unsere Funktion an jeder gewünschten Stelle mit dem Schlüsselwort await zu "pausieren".

async function logger() {
  // pause until fetch returns
  let data = await fetch('http://sampleapi.com/posts')
  console.log(data)
}

Dieser Code läuft und tut, was Sie wollen. Er protokolliert die Daten aus dem API-Aufruf. Wenn Ihr Gehirn nicht gerade explodiert ist, weiß ich nicht, wie ich Sie glücklich machen kann.

Der Vorteil davon ist, dass er intuitiv ist. Sie schreiben Code so, wie Ihr Gehirn darüber denkt, und sagen dem Skript, wo es pausieren soll.

Die anderen Vorteile sind, dass Sie try und catch auf eine Weise verwenden können, wie wir es mit Promises nicht konnten

async function logger ()  {
    try {
        let user_id = await fetch('/api/users/username')
        let posts = await fetch('/api/`${user_id}`')
        let object = JSON.parse(user.posts.toString())
        console.log(posts)
    } catch (error) {
        console.error('Error:', error) 
    }
}

Dies ist ein konstruiertes Beispiel, aber es beweist einen Punkt: catch fängt den Fehler ab, der während des gesamten Prozesses in irgendeinem Schritt auftritt. Es gibt mindestens 3 Stellen, an denen der try-Block fehlschlagen könnte, was dies mit Abstand zum saubersten Weg zur Fehlerbehandlung in asynchronem Code macht.

Wir können auch async-Funktionen mit Schleifen und Bedingungen ohne große Kopfschmerzen verwenden

async function count() {
    let counter = 1
    for (let i = 0; i < 100; i++) {
        counter += 1
        console.log(counter)
        await sleep(1000)
    }
}

Dies ist ein albernes Beispiel, aber es wird so funktionieren, wie Sie es erwarten würden, und es ist leicht zu lesen. Wenn Sie dies in der Konsole ausführen, sehen Sie, dass der Code bei der sleep-Funktion pausiert und die nächste Schleifeniteration erst nach einer Sekunde beginnt.

Das Kleingedruckte

Nachdem Sie von der Schönheit von async und await überzeugt sind, tauchen wir in die Details ein

  • async und await basieren auf Promises. Eine Funktion, die async verwendet, gibt selbst immer ein Promise zurück. Das ist wichtig zu bedenken und wahrscheinlich der größte "Stolperstein", auf den Sie stoßen werden.
  • Wenn wir await verwenden, pausiert es die Funktion, nicht den gesamten Code.
  • async und await sind nicht blockierend.
  • Sie können immer noch Promise-Helfer wie Promise.all() verwenden. Hier ist unser früheres Beispiel
    async function logPosts ()  {
        try {
            let user_id = await fetch('/api/users/username')
            let post_ids = await fetch('/api/posts/<code>${user_id}')
            let promises = post_ids.map(post_id => {
                return  fetch('/api/posts/${post_id}')
            }
            let posts = await Promise.all(promises)
            console.log(posts)
        } catch (error) {
            console.error('Error:', error) 
        }
    }
  • Await kann nur in Funktionen verwendet werden, die als Async deklariert wurden.
  • Daher können Sie await nicht im globalen Geltungsbereich verwenden.
    // throws an error
    function logger (callBack) {
        console.log(await callBack)
    }
    
    // works!
    async function logger () {
        console.log(await callBack)
    }

Jetzt verfügbar!

Die Schlüsselwörter async und await sind ab Juni 2017 in fast allen Browsern verfügbar. Noch besser: Um sicherzustellen, dass Ihr Code überall funktioniert, verwenden Sie Babel, um Ihren JavaScript-Code in eine ältere Syntax zu transpilieren, die ältere Browser unterstützen.

Wenn Sie mehr über die Angebote von ES2017 erfahren möchten, finden Sie hier eine vollständige Liste der ES2017-Features.