Asynchrone Programmierung mit async / await

Guten Tag, Freunde!

Relativ neue JavaScript-Ergänzungen sind asynchrone Funktionen und das Schlüsselwort await. Diese Funktionen sind im Grunde genommen syntaktischer Zucker über Versprechen (Versprechen), was das Schreiben und Lesen von asynchronem Code erleichtert. Sie lassen asynchronen Code synchron aussehen. Dieser Artikel hilft Ihnen herauszufinden, was was ist.

Bedingungen: Grundlegende Computerkenntnisse, Kenntnisse der Grundlagen von JS, Verständnis der Grundlagen von asynchronem Code und Versprechen.
Zweck: Zu verstehen, wie Versprechungen gemacht und wie sie verwendet werden.

Async / warte Grundlagen


Die Verwendung von async / await besteht aus zwei Teilen.

Asynchrones Schlüsselwort


Zunächst haben wir das Schlüsselwort async, das wir vor die Funktionsdeklaration setzen, um sie asynchron zu machen. Eine asynchrone Funktion ist eine Funktion, die die Möglichkeit vorwegnimmt, das Schlüsselwort await zum Ausführen von asynchronem Code zu verwenden.

Geben Sie Folgendes in die Browserkonsole ein:

function hello(){ return 'Hello' }
hello()

Die Funktion gibt 'Hallo' zurück. Nichts ungewöhnliches, oder?

Aber was ist, wenn wir daraus eine asynchrone Funktion machen? Versuche Folgendes:

async function hello(){ return 'Hello' }
hello()

Jetzt gibt ein Funktionsaufruf ein Versprechen zurück. Dies ist eines der Merkmale asynchroner Funktionen - sie geben Werte zurück, die garantiert in Versprechen umgewandelt werden.

Sie können auch asynchrone Funktionsausdrücke erstellen, z.

let hello = async function(){ return hello() }
hello()

Sie können auch Pfeilfunktionen verwenden:

let hello = async () => { return 'Hello' }

Alle diese Funktionen machen dasselbe.

Um den Wert eines abgeschlossenen Versprechens zu erhalten, können wir den Block .then () verwenden:

hello().then((value) => console.log(value))

... oder trotzdem:

hello().then(console.log)

Wenn Sie also das Schlüsselwort async hinzufügen, gibt die Funktion ein Versprechen anstelle eines Werts zurück. Darüber hinaus können synchrone Funktionen Overhead vermeiden, der mit dem Ausführen und Unterstützen der Verwendung von Warten verbunden ist. Ein einfaches Hinzufügen von Async vor der Funktion ermöglicht die automatische Codeoptimierung durch die JS-Engine. Cool!

Schlüsselwort warten


Die Vorteile asynchroner Funktionen werden noch deutlicher, wenn Sie sie mit dem Schlüsselwort await kombinieren. Es kann vor jeder versprechungsbasierten Funktion hinzugefügt werden, damit es auf den Abschluss des Versprechens wartet und dann das Ergebnis zurückgibt. Danach wird der nächste Codeblock ausgeführt.

Sie können await verwenden, wenn Sie eine Funktion aufrufen, die ein Versprechen zurückgibt, einschließlich Web-API-Funktionen.

Hier ist ein triviales Beispiel:

async function hello(){
    return greeting = await Promise.resolve('Hello')
}

hello().then(alert)

Natürlich ist der obige Code nutzlos, er dient nur als Demonstration der Syntax. Gehen wir weiter und schauen uns ein reales Beispiel an.

Umschreiben des Codes für Versprechen mit async / await


Nehmen Sie das Abrufbeispiel aus dem vorherigen Artikel:

fetch('coffee.jpg')
.then(response => response.blob())
.then(myBlob => {
    let objectURL = URL.createObjectURL(myBlob)
    let image = document.createElement('img')
    image.src = objectURL
    document.body.appendChild(image)
})
.catch(e => {
    console.log('There has been a problem with your fetch operation: ' + e.message)
})

Sie sollten bereits verstehen, was Versprechen sind und wie sie funktionieren, aber lassen Sie uns diesen Code mit async / await neu schreiben, um zu sehen, wie einfach es ist:

async function myFetch(){
    let response = await fetch('coffee.jpg')
    let myBlob = await response.blob()

    let objectURL = URL.createObjectURL(myBlob)
    let image = document.createElement('img')
    image.src = objectURL
    document.body.appendChild(image)
}

myFetch().catch(e => {
    console.log('There has been a problem with your fetch operation: ' + e.message)
})

Dies macht den Code viel einfacher und verständlicher - keine .then () -Blöcke!

Die Verwendung des Schlüsselworts async verwandelt eine Funktion in ein Versprechen, sodass wir einen gemischten Ansatz aus Versprechen verwenden und warten können, wobei der zweite Teil der Funktion in einem separaten Block hervorgehoben wird, um die Flexibilität zu erhöhen:

async function myFetch(){
    let response = await fetch('coffee.jpg')
    return await response.blob()
}

myFetch().then((blob) => {
    let objectURL = URL.createObjectURL(blob)
    let image = document.createElement('image')
    image.src = objectURL
    document.body.appendChild(image)
}).catch(e => console.log(e))

Sie können das Beispiel neu schreiben oder unsere Live-Demo ausführen (siehe auch Quellcode ).

Aber wie funktioniert es?


Wir haben den Code in die Funktion eingeschlossen und das Schlüsselwort async vor dem Schlüsselwort function hinzugefügt. Sie müssen eine asynchrone Funktion erstellen, um den Codeblock zu bestimmen, in dem der asynchrone Code ausgeführt wird. Warten funktioniert nur in asynchronen Funktionen.

Noch einmal: Warten funktioniert nur in asynchronen Funktionen.

Innerhalb der myFetch () -Funktion ähnelt der Code der Version für Versprechen, weist jedoch einige Unterschiede auf. Anstatt den Block .then () nach jeder versprechungsbasierten Methode zu verwenden, fügen Sie einfach das Schlüsselwort await hinzu, bevor Sie die Methode aufrufen, und weisen Sie der Variablen einen Wert zu. Das Schlüsselwort await bewirkt, dass die JS-Engine die Codeausführung in einer bestimmten Zeile anhält, sodass ein anderer Code ausgeführt werden kann, bis die asynchrone Funktion ein Ergebnis zurückgibt. Sobald es ausgeführt wird, setzt der Code die Ausführung ab der nächsten Zeile fort.
Zum Beispiel:

let response = await fetch('coffee.jpg')

Der vom Versprechen fetch () zurückgegebene Wert wird der Antwortvariablen zugewiesen, wenn der angegebene Wert verfügbar wird, und der Parser stoppt in dieser Zeile, bis das Versprechen abgeschlossen ist. Sobald der Wert verfügbar ist, fährt der Parser mit der nächsten Codezeile fort, die den Blob erstellt. Diese Zeile nennt auch die versprechungsbasierte asynchrone Methode, daher verwenden wir hier auch await. Wenn das Ergebnis der Operation zurückgegeben wird, geben wir es von der Funktion myFetch () zurück.

Dies bedeutet, dass beim Aufrufen der Funktion myFetch () ein Versprechen zurückgegeben wird, sodass wir .then () hinzufügen können, in dem die Anzeige des Bildes auf dem Bildschirm erfolgt.

Sie denken wahrscheinlich "Das ist großartig!" Und Sie haben Recht - weniger .then () Blöcke zum Umschließen des Codes, alles sieht aus wie synchroner Code, also ist es intuitiv.

Fehlerbehandlung hinzufügen


Wenn Sie die Fehlerbehandlung hinzufügen möchten, haben Sie mehrere Möglichkeiten.

Sie können die synchrone Struktur try ... catch mit async / await verwenden. Dieses Beispiel ist eine erweiterte Version des obigen Codes:

async function myFetch(){
    try{
        let response = await fetch('coffee.jpg')
        let myBlob = await response.blob()

        let objectURL = URL.createObjectURL(myBlob)
        let image = document.createElement('img')
        image.src = objectURL
        document.body.appendChild(image)
    } catch(e){
        console.log(e)
    }
}

myFetch()

Der catch () {} -Block akzeptiert ein Fehlerobjekt, das wir "e" genannt haben. Jetzt können wir es an die Konsole ausgeben. Dadurch erhalten wir eine Nachricht darüber, wo der Fehler im Code aufgetreten ist.

Wenn Sie die zweite Version des oben gezeigten Codes verwenden möchten, sollten Sie einfach den Hybridansatz weiter verwenden und den Block .catch () am Ende des Aufrufs .then () wie folgt hinzufügen:

async function myFetch(){
    let response = await fecth('coffee.jpg')
    return await response.blob()
}

myFetch().then((blob) => {
    let objectURL = URL.createObjectURL
    let image = document.createElement('img')
    image.src = objectURL
    document.body.appendChild(image)
}).catch(e => console.log(e))

Dies ist möglich, weil der Block .catch () Fehler abfängt, die sowohl in der asynchronen Funktion als auch in der Versprechenskette auftreten. Wenn Sie hier den try / catch-Block verwenden, können Sie keine Fehler behandeln, die beim Aufruf der Funktion myFetch () auftreten.

Sie finden beide Beispiele auf GitHub:
simple-fetch-async-await-try-catch.html (siehe Quelle )

simple-fetch-async-await-versprechen-catch.html (siehe Quelle )

Warten auf Promise.all ()


Async / await basiert auf Versprechungen, sodass Sie letztere voll ausnutzen können. Dazu gehört insbesondere Promise.all (). Sie können Promise.all () ganz einfach mit wait hinzufügen, um alle Rückgabewerte ähnlich wie bei synchronem Code zu schreiben. Nehmen Sie wieder das Beispiel aus dem vorherigen Artikel . Lassen Sie einen Tab offen, um ihn mit dem unten gezeigten Code zu vergleichen.

Mit async / await (siehe Live-Demo und Quellcode ) sieht es so aus:

async function fetchAndDecode(url, type){
    let repsonse = await fetch(url)

    let content

    if(type === 'blob'){
        content = await response.blob()
    } else if(type === 'text'){
        content = await response.text()
    }

    return content
}

async function displayContent(){
    let coffee = fetchAndDecode('coffee.jpg', 'blob')
    let tea = fetchAndDecode('tea.jpg', 'blob')
    let description = fetchAndDecode('description.txt', 'text')

    let values = await Promise.all([coffee, tea, description])

    let objectURL1 = URL.createObjectURL(values[0])
    let objectURL2 = URL.createObjectURL(values[1])
    let descText = values[2]

    let image1 = document.createElement('img')
    let image2 = document.createElement('img')
    image1.src = objectURL1
    image2.src = objectURL2
    document.body.appendChild(image1)
    document.body.appendChild(image2)

    let para = document.createElement('p')
    para.textContent = descText
    document.body.appendChild(para)
}

displayContent()
.catch(e => console.log(e))

Wir haben die Funktion fetchAndDecode () mit ein paar Änderungen leicht asynchron gemacht. Achten Sie auf die Linie:

let values = await Promise.all([coffee, tea, description])

Mit await erhalten wir die Ergebnisse von drei Versprechungen in der Wertevariablen, ähnlich wie bei synchronem Code. Wir müssen die gesamte Funktion in eine neue asynchrone Funktion, displayContent (), einschließen. Wir haben keine starke Code-Reduzierung erzielt, konnten jedoch den größten Teil des Codes aus dem .then () -Block extrahieren, was eine nützliche Vereinfachung bietet und den Code besser lesbar macht.

Um Fehler zu behandeln, haben wir unserem Aufruf von displayContent () einen .catch () -Block hinzugefügt. Es behandelt Fehler beider Funktionen.

Denken Sie daran: Sie können auch den Block .finally () verwenden, um einen Bericht über den Vorgang abzurufen. Sie können ihn in unserer Live-Demo in Aktion sehen (siehe auch den Quellcode ).

Async / warten Nachteile


Async / await hat ein paar Mängel.

Async / await lässt den Code synchron aussehen und verhält sich gewissermaßen synchroner. Das Schlüsselwort await blockiert die Ausführung des darauf folgenden Codes, bis das Versprechen erfüllt ist, wie dies bei einer synchronen Operation der Fall ist. Auf diese Weise können Sie andere Aufgaben ausführen, Ihr eigener Code ist jedoch gesperrt.

Dies bedeutet, dass Ihr Code durch eine große Anzahl ausstehender Versprechen nacheinander verlangsamt werden kann. Jedes Warten wartet auf den Abschluss des vorherigen, während wir möchten, dass die Versprechen gleichzeitig erfüllt werden, als ob wir nicht async / await verwenden würden.

Es gibt ein Entwurfsmuster, um dieses Problem zu mindern: Deaktivieren Sie alle Versprechen-Prozesse, indem Sie Versprechen-Objekte in Variablen speichern und dann warten. Schauen wir uns an, wie dies implementiert wird.

Wir haben zwei Beispiele zur Verfügung: slow-async-await.html (siehe Quellcode ) und fast-async-await.html (siehe Quellcode ). Beide Beispiele beginnen mit einer Versprechen-Funktion, die eine asynchrone Operation mit setTimeout () nachahmt:

function timeoutPromise(interval){
    return new Promise((resolve, reject) => {
        setTimeout(function(){
            resolve('done')
        }, interval)
    })
}

Dann folgt die asynchrone Funktion timeTest (), die drei Aufrufe von timeoutPromise () erwartet:

async function timeTest(){
    ...
}

Jeder der drei Aufrufe von timeTest () endet mit einer Aufzeichnung der Zeit, die zur Erfüllung des Versprechens benötigt wurde. Anschließend wird die Zeit aufgezeichnet, die zur Durchführung des gesamten Vorgangs benötigt wird:

let startTime = Date.now()
timeTest().then(() => {
    let finishTime = Date.now()
    let timeTaken = finishTime - startTime
    alert('Time taken in milliseconds: ' + timeTaken)
})

In jedem Fall ist die Funktion timeTest () unterschiedlich.

In slow-async-await.html sieht timeTest () folgendermaßen aus:

async function timeTest(){
    await timeoutPromise(3000)
    await timeoutPromise(3000)
    await timeoutPromise(3000)
}

Hier erwarten wir nur drei Aufrufe von timeoutPromise, wobei jedes Mal eine Verzögerung von 3 Sekunden eingestellt wird. Jeder Anruf wartet auf den Abschluss des vorherigen. Wenn Sie das erste Beispiel ausführen, wird in ca. 9 Sekunden ein modales Fenster angezeigt.

In fast-async-await.html sieht timeTest () folgendermaßen aus:

async function timeTest(){
    const timeoutPromise1 = timeoutPromise(3000)
    const timeoutPromise2 = timeoutPromise(3000)
    const timeoutPromise3 = timeoutPromise(3000)

    await timeoutPromise1
    await timeoutPromise2
    await timeoutPromise3
}

Hier speichern wir drei Promise-Objekte in Variablen, wodurch die damit verbundenen Prozesse gleichzeitig ausgeführt werden.

Des Weiteren erwarten wir ihre Ergebnisse - wenn Versprechen gleichzeitig erfüllt werden, werden Versprechen auch gleichzeitig erfüllt; Wenn Sie das zweite Beispiel ausführen, sehen Sie in ca. 3 Sekunden ein modales Fenster!

Sie sollten den Code sorgfältig testen und dies berücksichtigen, während Sie die Leistung verringern.

Eine weitere kleine Unannehmlichkeit ist die Notwendigkeit, die erwarteten Versprechen in eine asynchrone Funktion zu packen.

Verwenden von async / await mit Klassen


Zusammenfassend stellen wir fest, dass Sie auch bei Methoden zum Erstellen von Klassen Async hinzufügen können, damit diese Versprechen zurückgeben und auf darin enthaltene Versprechen warten. Nehmen Sie den Code aus dem Artikel über objektorientiertes JS und vergleichen Sie ihn mit der mit async geänderten Version:

class Person{
    constructor(first, last, age, gender, interests){
        this.name = {
            first,
            last
        }
        this.age = age
        this.gender = gender
        this.interests = interests
    }

    async greeting(){
        return await Promise.resolve(`Hi! I'm ${this.name.first}`)
    }

    farewell(){
        console.log(`${this.name.first} has left the building. Bye for now!`)
    }
}

let han = new Person('Han', 'Solo', 25, 'male', ['Smuggling'])

Die Klassenmethode kann wie folgt verwendet werden:

han.greeting().then(console.log)

Browser-Unterstützung


Eines der Hindernisse für die Verwendung von async / await ist die mangelnde Unterstützung älterer Browser. Diese Funktion ist in fast allen modernen Browsern sowie in Versprechungen verfügbar. In Internet Explorer und Opera Mini gibt es einige Probleme.

Wenn Sie async / await verwenden möchten, aber die Unterstützung älterer Browser benötigen, können Sie die BabelJS- Bibliothek verwenden. Sie können die neueste JS verwenden und diese in eine browserspezifische konvertieren.

Fazit


Mit Async / await können Sie asynchronen Code schreiben, der einfach zu lesen und zu warten ist. Obwohl Async / Awarit schlechter unterstützt wird als andere Methoden zum Schreiben von asynchronem Code, lohnt es sich auf jeden Fall, dies zu untersuchen.

Vielen Dank für Ihre Aufmerksamkeit.

Viel Spaß beim Codieren!

All Articles