Eine kurze Geschichte der asynchronen Javascript-Funktionen

Während ich Javascript studierte, stieß ich immer wieder auf zahlreiche Artikel über asynchrone Funktionen und Operationen. Trotz der unbestrittenen Vorteile einer solchen Funktion wurde ich jedes Mal durch die von den Autoren zitierte Auflistung in Schwierigkeiten gebracht. Die Worte änderten sich, die Essenz blieb dieselbe, Brei braute sich in meinem Kopf zusammen. Under the Cut - ein kleiner Leitfaden zur historischen Entwicklung und zu den Versionen von ECMA.

Warum brauchen wir asynchrone Operationen?


Ein Computerprogramm kann eine unbegrenzte Anzahl von Aufgaben ausführen. Es ist kein Geheimnis, dass Webanwendungen mit vielen verschiedenen Aufgaben arbeiten müssen, für die häufig dieselben Daten verwendet werden müssen. Eines der häufigsten Beispiele ist das Anzeigen von Benutzerinformationen (UI) und das Abrufen von Informationen mithilfe von Serveranforderungen. Es überrascht nicht, dass fast jeder Webentwickler damit konfrontiert ist: Arbeiten mit einer bestimmten Datenbank, Bereitstellen einer Benutzeroberfläche, Organisieren einer API - all dies ist buchstäblich in jeder Testaufgabe nicht nur von JS-Programmierern enthalten.

Warum nicht Befehle nacheinander ausführen?

Oft können die vom Benutzer benötigten Informationen erst nach einer beträchtlichen Zeitspanne erhalten werden. Wenn Sie das Programm wie folgt organisieren:

  1. Abrufen von Informationen von der Website https: / some / api / item / 1
  2. Zeigen Sie Informationen zum ersten Element auf dem Bildschirm an.

Es treten ernsthafte Schwierigkeiten beim Rendern der Seite und beim Erzeugen eines angenehmen Eindrucks beim Benutzer auf (die sogenannte Benutzererfahrung). Stellen Sie sich vor: Eine Seite, beispielsweise Netflix oder Aliexpress, muss Daten aus Hunderten von Datenbanken abrufen, bevor der Benutzer den Inhalt anzeigen kann. Eine solche Verzögerung ähnelt dem Laden eines 3D-Spiellevels. Wenn der Spieler bereit ist zu warten, möchte der Benutzer der Website im Moment die meisten Informationen erhalten.

Die Lösung wurde gefunden: asynchrone Operationen . Während der Haupt-Thread des Programms damit beschäftigt ist, Website-Elemente auf Leinwand zu initialisieren und anzuzeigen, gibt er auch Aufgaben an die anderen Threads aus, um „ die Waren für den Benutzer zu erhalten “. Sobald dieser Thread seine Arbeit abgeschlossen hat, „setzen“ sich die Informationen im Hauptthread ab und werden zur Anzeige verfügbar. Auf der Webseite selbst befindet sich ein bestimmter Platzhalter - ein Objekt, das Platz für zukünftige Informationen einnimmt.

Bild

Zu diesem Zeitpunkt wird die Seite bereits angezeigt, obwohl einige Anforderungen noch nicht bestanden wurden.

Bild

Höchstwahrscheinlich geben einige weitere Anforderungen am Ende der Seite einen Wert zurück, und die Seite wird weiterhin aktualisiert und dynamisch gerendert, ohne dass dies für den Benutzer unangenehm ist.

ES5 und früher: Rückruf


Bevor wir mit der Überprüfung von Rückrufen fortfahren, werfen wir einen Blick auf die Funktionen höherer Ordnung .

Eine Funktion höherer Ordnung in JS ist eine Funktion, die eine andere Funktion als Argument verwendet . Hier ist ein Beispiel:

objectIsString(objectRef) { return typeof(objectRef) === 'String'; } listOfObjects.filter(objectIsString); 

Daher wurde die Funktion objectIsString an die Funktion höherer Ordnung - filter - übergeben, mit der listOfObjects gefiltert und nur Objekte vom Typ string in der Liste belassen werden können.
Rückrufe funktionieren ähnlich. Dies ist eine Funktion, die als Argument an eine andere Funktion übergeben wird. Am häufigsten wird die Funktion setTimeout als Beispiel für eine Funktion verwendet, die Rückrufe verarbeitet. Im Allgemeinen wird dies als setTimeout (function, timeoutValue) verwendet, wobei function eine Rückruffunktion ist, die vom Browser nach einem im Timeout angegebenen Zeitraum ausgeführt wird.

 setTimeout(console.log(1), 2000); console.log(2); 


Drucken 2 1.

ES 6: Versprechen


In Standard 6 wurde ein neuer Typ eingeführt - Versprechen (Versprechen, im Folgenden - Versprechen). Ein Versprechen ist ein Typ, dessen Objekte einen von drei Zuständen haben: ausstehend, erfüllt, abgelehnt. Darüber hinaus können Sie mit den letzten beiden Zuständen Funktionen - Rückrufe - „verknüpfen“. Sobald der im Rahmen des Versprechens selbst beschriebene asynchrone Prozess zum Erfolg / Misserfolg führt, wird die damit verbundene Funktion aufgerufen. Dieser Prozess wird als "hängende Rückrufe" bezeichnet und mit den Methoden "then" und "catch" des Versprechens selbst ausgeführt. Der Unterschied besteht darin, dass beim Aufrufen der Argumente zwei Funktionen übertragen werden - bei Erfolg (onFullfillment) und Misserfolg (onRejected), während catch, da es nicht schwer zu erraten ist, nur eine Funktion zum Verarbeiten von Fehlern in einem Versprechen akzeptiert. Um festzustellen, ob ein Versprechen in einem bestimmten Fall erfolgreich ausgeführt wurde, und um das zurückgegebene Ergebnis zu parametrisieren

Lassen Sie uns schrittweise ein Versprechen erstellen und verwenden.

 // : let promise; //     Promise. let promise = new Promise((resolve, reject) => { }); //  ,  . let promise = new Promise((resolve, reject) => { setTimeout(() => { resolve("result"); }, 1000); }); 

Fügen Sie nun Ereignishandler mit der then-Methode hinzu. Das Argument für die Funktion, die den Erfolg behandelt, ist das Ergebnis, während das Argument für die Funktion, die den Fehler des Versprechens behandelt, ein Fehler ist.

 promise .then( result => { }, error => { } ); //     – . promise .then( result => { //  - -    resolve alert("Fulfilled: " + result); // result -  resolve }, error => { //   -    reject alert("Rejected: " + error); // error -  reject } ); 

Fertig!

Wir werden also noch einmal kurz den Prozess der Erstellung eines Versprechens beschreiben:

  1. Initialisieren Sie das Objekt (neues Versprechen)
  2. Wir übergeben die Funktion der Auflösung und / oder Ablehnung als einziges Argument an den Konstruktor. Eine Funktion muss mindestens 1 asynchronen Betrieb haben
  3. Mit den then / catch-Methoden fügen wir Funktionen hinzu - Ergebnishandler.

Generatoren. Ausbeute


Auch im ES6-Standard wurde ein neuer Funktionstyp definiert - Generatoren. Diese Funktionen können auf den ersten Blick bei identischen Aufrufen mehrmals unterschiedliche Werte zurückgeben. Mal sehen, wie sie es machen und warum sie es benutzen.

Die Standardform des Generators: function * functionName () {}. Im Hauptteil der Funktionen selbst wird das Wort Ausbeute verwendet, um einen Zwischenwert zurückzugeben.

Betrachten Sie als Beispiel den folgenden Generator:

 function* generateNumber() { yield 1; yield 2; return 3; } 

Im Moment befindet sich der Generator am Anfang seiner Ausführung. Bei jedem Aufruf der nächsten Generatormethode wird der vor der nächsten Ausbeute (oder Rückgabe) beschriebene Code ausgeführt, und der in der Zeile mit einem dieser Wörter angegebene Wert wird ebenfalls zurückgegeben.

 Let one = generateNumber.next(); // {value: 1, done: false} 

Der nächste Aufruf gibt den Wert 2 auf die gleiche Weise zurück. Der dritte Aufruf gibt den Wert 3 zurück und beendet die Ausführung der Funktion.

 Let two = generateNumber.next(); // {value: 2, done: false} Let three = generateNumber.next(); // {value: 3, done: false} 

Trotzdem kann über die nächste Funktion auf den Generator zugegriffen werden. Es wird jedoch der gleiche Wert zurückgegeben: das Objekt {done: true}.

ES7. Async / warten


Zusammen mit dem Wunsch, OOP-Liebhabern mithilfe syntaktischer Zuckerklassen und Nachahmung der Vererbung zu gefallen, versuchen die Entwickler von ES7, das Verständnis von Javascript zu erleichtern und Liebhabern das Schreiben von synchronem Code zu ermöglichen. Mit asynchronen / wartenden Konstrukten kann der Benutzer asynchronen Code so ähnlich wie möglich zu synchron schreiben. Falls gewünscht, können Sie die kürzlich untersuchten Versprechen loswerden und den Code mit minimalen Änderungen neu schreiben.
Betrachten Sie ein Beispiel:

Versprechen verwenden:

 requestBook(id) { return bookAPIHelper.getBook(id).then(book => {console.log(book)}); } 

Verwenden von async / await.

 async requestBook(id) { Const book = await bookAPIHelper.getBook(id); Console.log(book); } 

Beschreiben wir, was wir gesehen haben:

1) Async - Schlüsselwort, das beim Deklarieren einer asynchronen Funktion hinzugefügt wird
2) Warten - ein Schlüsselwort, das beim Aufrufen einer asynchronen Funktion hinzugefügt wird.

ES8. Asynchrone Iteration


Das synchrone Durchlaufen von Daten wurde bereits in ES5 möglich. Nach zwei Spezifikationen wurde beschlossen, die Möglichkeit einer asynchronen Iteration in asynchronen Datenquellen hinzuzufügen. Wenn next () aufgerufen wird, gibt es nicht {value, done} zurück, sondern ein Versprechen (siehe ES6).

Schauen wir uns die Funktion createAsyncIterable (iterable) an.

 async function* createAsyncIterable(iterable) { for (const elem of iterable) { yield elem; } } 

Wie Sie sehen können, initialisiert die Funktion die Sammlung für jeden Aufruf der Elemente, für die ein Versprechen mit dem in iterable angegebenen Wert zurückgegeben wird.

 const asyncIterable = createAsyncIterable(['async 1', 'async 2']); const asyncIterator = asyncIterable[Symbol.asyncIterator](); asyncIterator.next() .then(result => { console.log(result); // { // value: 'async 1', // done: false, // } return asyncIterator.next(); }) .then(result => { console.log(result); // { // value: 'async 2', // done: false, // } return asyncIterator.next(); }) .then(result => { console.log(result); // { // value: 'undefined', // done: true, // } }); 

Darüber hinaus definiert der neue Standard eine Warteschleife, die für solche Operationen geeignet ist.

 for await (const x of createAsyncIterable(['a', 'b'])) 

TL; DR


Es ist überhaupt nicht notwendig, auswendig zu wissen und sich zu merken, zu welcher Version von ECMAScript diese oder jene Syntax gehört, insbesondere wenn Sie gerade erst mit dem asynchronen Verhalten in JS vertraut geworden sind. Gleichzeitig ermöglicht das Studium der Asynchronität in genau der Reihenfolge, die in der Geschichte der Entwicklung von Spezifikationen vorgeschlagen wurde, dem Programmierer, die an die JS-Engine übergebene Syntax und Anweisungen perfekt zu verstehen, aber auch der Logik der Verbesserung von ECMAScript als Produkt zu folgen, die von JS-Entwicklern diktierten Trends zu verstehen, sie zu trennen und zu akzeptieren .

Kurz gesagt:

Rückrufe <= ES5
Versprechen, Ertrag (Generatoren): ES6
Async / warten: ES7
Asynchrone Iteratoren: ES8

Source: https://habr.com/ru/post/de434140/


All Articles