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.
- Bestellen Sie Ihr Essen.
- Bezahlen Sie für Ihr Essen und erhalten Sie ein Ticket mit einer Bestellnummer.
- Warten Sie auf Ihr Essen.
- Wenn Ihr Essen fertig ist, rufen sie Ihre Ticketnummer auf.
- 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
asyncundawaitbasieren auf Promises. Eine Funktion, dieasyncverwendet, 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
awaitverwenden, pausiert es die Funktion, nicht den gesamten Code. asyncundawaitsind nicht blockierend.- Sie können immer noch Promise-Helfer wie
Promise.all()verwenden. Hier ist unser früheres Beispielasync 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
awaitnicht 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.
Die JavaScript-Syntax wird der von C#! ähneln, das ist cool! Besonders für .NET-Entwickler :)
Schauen Sie sich die obigen Codebeispiele an: Ersetzen Sie
letdurchvar(was tatsächlich ein gültiges JS- und C#-Schlüsselwort ist) undfunctiondurchTask(oderTask<ReturnType>). Das IST C#-Code!Was für ein Juwel.
Macht await asynchrone Aufrufe nicht synchron?
Nein, tut es nicht. Und genau deshalb müssen Sie
awaitausschließlich innerhalb von async-Funktionen verwenden.Sozusagen, aber es tut dies auf nicht blockierende Weise. Der Vorteil dabei ist, dass andere asynchrone Funktionen weiterhin laufen können, während wir warten. Wenn Sie zum Beispiel in einem Funktionsaufruf auf eine sleep-Funktion warten würden, könnte ein Ereignis aus einem anderen Codeteil immer noch ausgelöst werden.
Der folgende Code sollte "main start", dann "interrupting the middle of main" und schließlich "main end" ausgeben.
Im Wesentlichen ist async/await eine Möglichkeit, eine einzelne Aufgabe synchron zu gestalten, ohne die JavaScript-Ereignisschleife zu blockieren.
Das ist der Kern der Sache.
Die einzige Einschränkung ist, dass es ein Promise-Wrapper ist, daher müssen Sie eine Promise-Rückgabe erwarten. In 90 % der Fälle ist das kein Problem, aber es kann Probleme verursachen, wenn Sie es nicht erwarten.
Nein. Es sieht aus, als wäre es asynchron.
awaitführt im Grunde syntaktischen Zucker ein. Zum Beispielkann "transpiliert" werden zu
Kurz gesagt, eine
async-Funktion gibt immer einPromisezurück, undawaitist Syntax, um den aufgelösten Wert zu erhalten (und das üblichetry...catchfür den abgelehnten).Ich vermute, die sleep-Funktion wird so aussehen.
Es könnte vereinfacht werden zu
Bearbeitet: Klammer vergessen
Schöner Artikel, aber ich mag das fehlende Semikolon nicht :D
Schöner Artikel – vielleicht die Betonung auf die Behandlung von abgelehnten Promises legen
Das ist ein Game Changer :0
Nein, bin ich nicht
Ich habe die Idee, dass man sich asynchrone Aufgaben wie das Bestellen von etwas bei Amazon vorstellen kann: Man kauft etwas, aber dann hört man nicht sofort auf, man kann in der Zwischenzeit andere Dinge tun.
Es ist erwähnenswert, dass
awaitauf oberster Ebene nicht verfügbar ist. Was ich meine ist, dass Sie derzeit nicht die Konsole öffnen undawait fetch("https/css-tricks.com")eingeben können. Sie müssen es in eineasyncIIFE einpacken.Es gibt eine Diskussion darüber, ob dies erlaubt werden soll, aber es gibt viele Probleme damit.
In den neuesten Dev-Builds von Chrome gibt es
awaitauf oberster Ebene: https://developers.google.com/web/updates/2017/08/devtools-release-notes#awaitBedeutet das, ich kann das tun
logPosts().then( results => mylog(results))
E
Was Sie beschreiben, ist die Promise-API, die, wie im Beitrag erwähnt, schon seit einiger Zeit verfügbar ist.