Sogar Kinder werden verstehen: eine einfache Erklärung von Async / Warten und Versprechen in JavaScript

Hallo habr Ich präsentiere Ihnen die Übersetzung des Artikels „JavaScript Async / Await and Promises: Erklärt, als wären Sie fünf Jahre alt“ von Jack Pordi.

Jeder, der sich irgendwann als JavaScript-Entwickler betrachtet, sollte auf Callback-Funktionen, Versprechungen oder in jüngerer Zeit auf die async / await-Syntax gestoßen sein. Wenn Sie lange genug im Spiel waren, sind Sie wahrscheinlich auf Zeiten gestoßen, in denen verschachtelte Rückruffunktionen der einzige Weg waren, um eine Asynchronität in JavaScript zu erreichen.

Als ich anfing, in JavaScript zu lernen und zu schreiben, gab es bereits eine Milliarde Tutorials und Tutorials, in denen erklärt wurde, wie Asynchronität in JavaScript erreicht werden kann. Viele von ihnen erklärten jedoch einfach, wie man Rückruffunktionen in Versprechungen oder Versprechungen in async / await umwandelt. Für viele ist dies wahrscheinlich mehr als genug, um mit ihnen auszukommen und sie in ihrem Code zu verwenden.

Wenn Sie jedoch wie ich die asynchrone Programmierung (und nicht nur die JavaScript-Syntax!) Wirklich verstehen möchten, stimmen Sie mir möglicherweise zu, dass es an Materialien mangelt, die die asynchrone Programmierung von Grund auf erklären.

Was bedeutet asynchron?


Das Bild zeigt eine denkende Person

Wenn Sie diese Frage stellen, können Sie in der Regel Folgendes hören:

  • Es gibt mehrere Threads, die gleichzeitig Code ausführen.
  • Es wird mehr als ein Codeteil gleichzeitig ausgeführt.
  • Das ist Parallelität.

Bis zu einem gewissen Grad sind alle Optionen korrekt. Aber anstatt Ihnen eine technische Definition zu geben, die Sie wahrscheinlich bald vergessen werden, werde ich ein Beispiel geben, das selbst ein Kind verstehen kann .

Lebensbeispiel


Das Bild zeigt Gemüse und ein Küchenmesser

Stellen Sie sich vor, Sie kochen Gemüsesuppe. Nehmen wir für eine gute und einfache Analogie an, dass eine Gemüsesuppe nur aus Zwiebeln und Karotten besteht. Das Rezept für eine solche Suppe kann wie folgt lauten:

  1. Karotten hacken.
  2. Zwiebel hacken.
  3. Geben Sie Wasser in die Pfanne, schalten Sie den Herd ein und warten Sie, bis er kocht.
  4. Die Karotten in die Pfanne geben und 5 Minuten ruhen lassen.
  5. Die Zwiebeln in die Pfanne geben und weitere 10 Minuten kochen lassen.

Diese Anweisungen sind einfach und verständlich, aber wenn einer von Ihnen, der dies liest, wirklich weiß, wie man kocht, können Sie sagen, dass dies nicht die effektivste Art des Kochens ist. Und Sie werden Recht haben, deshalb:

  • Die Schritte 3, 4 und 5 erfordern nicht, dass Sie als Koch etwas anderes tun, als den Prozess zu beobachten und die Zeit im Auge zu behalten.
  • In den Schritten 1 und 2 müssen Sie aktiv etwas unternehmen.

Daher kann das Rezept für einen erfahrenen Koch wie folgt lauten:

  1. Fangen Sie an, einen Topf Wasser zu kochen.
  2. Während Sie darauf warten, dass der Topf kocht, beginnen Sie mit dem Schneiden der Karotten.
  3. Wenn Sie mit dem Hacken der Karotten fertig sind , sollte das Wasser kochen, also fügen Sie die Karotten hinzu.
  4. Während die Karotten in einer Pfanne gekocht werden, die Zwiebeln hacken.
  5. Zwiebeln hinzufügen und weitere 10 Minuten kochen.

Trotz der Tatsache, dass alle Aktionen gleich geblieben sind, können Sie davon ausgehen, dass diese Option viel schneller und effizienter ist. Dies ist genau das Prinzip der asynchronen Programmierung: Sie möchten sich nie zurücklehnen und nur auf etwas warten, während Sie Ihre Zeit für andere nützliche Dinge aufwenden können.

Wir alle wissen, dass beim Programmieren ziemlich oft auf etwas gewartet wird - ob auf eine HTTP-Antwort von einem Server oder auf eine Aktion eines Benutzers oder auf etwas anderes. Aber die Ausführungszyklen Ihres Prozessors sind kostbar und sollten immer aktiv genutzt werden, etwas tun und nicht erwarten: Dies führt zu einer asynchronen Programmierung .

Kommen wir jetzt zu JavaScript, okay?


Nach dem gleichen Beispiel einer Gemüsesuppe werde ich einige Funktionen schreiben, um die Schritte des oben beschriebenen Rezepts darzustellen.

Schreiben wir zunächst synchrone Funktionen, die Aufgaben darstellen, die keine Zeit in Anspruch nehmen. Dies sind die guten alten JavaScript-Funktionen, aber beachten Sie, dass ich die chopOnions chopCarrots und chopOnions als Aufgaben beschrieben habe, die aktive Arbeit (und Zeit) erfordern, sodass sie einige lange Berechnungen durchführen können. Der vollständige Code ist am Ende des Artikels [1] verfügbar.

 function chopCarrots() { /*   ... */ console.log(" !"); } function chopOnions() { /*   ... */ console.log(" !"); } function addOnions() { console.log("   !"); } function addCarrots() { console.log("   !"); } 

Bevor ich auf asynchrone Funktionen übergehe, werde ich zunächst kurz erläutern, wie das JavaScript-Typensystem mit Asynchronität umgeht: Grundsätzlich sollten alle Ergebnisse (einschließlich Fehler) von asynchronen Operationen in die Zusage (n) eingeschlossen werden .

Damit eine Funktion ein Versprechen zurückgibt, können Sie:

  • das Versprechen ausdrücklich zurückgeben, d.h. return new Promise(…) ;
  • Ein Versprechen implizit zurückgeben - async zur Funktionsdeklaration hinzu, d. async async function foo() ;
  • Verwenden Sie beide Optionen .

Es gibt einen ausgezeichneten Artikel [2], der über den Unterschied zwischen asynchronen Funktionen und Funktionen spricht, die ein Versprechen zurückgeben. In meinem Artikel werde ich mich daher nicht mit diesem Thema befassen. Das Wichtigste ist, dass Sie in asynchronen Funktionen immer das async sollten.

Unsere asynchronen Funktionen, die die Schritte 3 bis 5 der Zubereitung von Gemüsesuppe darstellen, lauten wie folgt:

 async function letPotKeepBoiling(time) { return; //  ,      } async function boilPot() { return; //  ,       } 

Ich habe die Implementierungsdetails erneut gelöscht, um nicht von ihnen abgelenkt zu werden, aber sie werden am Ende des Artikels veröffentlicht [1].

Es ist wichtig zu wissen, dass Sie, um auf das Ergebnis des Versprechens zu warten, später etwas damit anfangen können, einfach das Schlüsselwort await :

 async function asyncFunction() { /*  ... */ } result = await asyncFunction(); 

Jetzt müssen wir nur noch alles zusammenfügen:

 function makeSoup() { const pot = boilPot(); chopCarrots(); chopOnions(); await pot; addCarrots(); await letPotKeepBoiling(5); addOnions(); await letPotKeepBoiling(10); console.log("   !"); } makeSoup(); 

Aber warte! Das funktioniert nicht! Sie sehen den SyntaxError: await is only valid in async functions . Warum? Denn wenn Sie eine Funktion nicht mit dem async , definiert JavaScript diese standardmäßig als synchrone Funktion - und synchron bedeutet kein Warten! [3] Dies bedeutet auch, dass Sie das Warten nicht außerhalb einer Funktion verwenden können.

Deshalb fügen wir der makeSoup Funktion einfach das async makeSoup :

 async function makeSoup() { const pot = boilPot(); chopCarrots(); chopOnions(); await pot; addCarrots(); await letPotKeepBoiling(5); addOnions(); await letPotKeepBoiling(10); console.log("   !"); } makeSoup(); 

Und voila! Beachten Sie, dass ich in der zweiten Zeile die asynchrone Funktion boilPot ohne das Schlüsselwort await boilPot , da wir nicht warten möchten, bis die Pfanne kocht, bevor wir mit dem Schneiden der Karotten beginnen. Wir erwarten nur das Versprechen eines pot in der fünften Zeile, bevor wir die Karotten in die Pfanne legen müssen, weil wir dies nicht tun wollen, bevor das Wasser kocht.

Was passiert beim await Anrufe? Nun, nichts ... irgendwie ...

Im Kontext der Funktion makeSoup können Sie sich das einfach so makeSoup als würden Sie erwarten, dass etwas passiert (oder ein Ergebnis, das irgendwann zurückgegeben wird).

Aber denken Sie daran: Sie (wie Ihr Prozessor) werden niemals einfach nur da sitzen und auf etwas warten wollen, während Sie Ihre Zeit für andere Dinge aufwenden können .

Anstatt nur Suppe zu kochen, könnten wir also etwas anderes parallel kochen:

 makeSoup(); makePasta(); 

Während wir auf letPotKeepBoiling warten, können wir zum Beispiel Pasta kochen.

Sehen Sie? Die asynchrone / warten-Syntax ist eigentlich ziemlich einfach zu verwenden, wenn Sie es verstehen, stimmen Sie zu?

Was ist mit offenen Versprechungen?


Nun, wenn Sie darauf bestehen, werde ich mich der Verwendung expliziter Versprechen zuwenden ( ca. Übersetzt: Durch explizite Versprechen impliziert der Autor direkt die Syntax der Versprechen und durch implizite Versprechen die Syntax async / await, da sie das Versprechen implizit zurückgibt - keine Notwendigkeit zum Schreiben return new Promise(…) ). Beachten Sie, dass die Async / Warten-Methoden auf den Versprechungen selbst basieren und daher beide Optionen vollständig kompatibel sind .

Meiner Meinung nach liegen explizite Versprechungen zwischen den alten Rückrufen und der neuen asynchronen / erwarteten sexuellen Syntax. Alternativ können Sie sich die asynchrone / wartende sexuelle Syntax auch als nichts anderes als implizite Versprechen vorstellen. Am Ende folgte das async / await-Konstrukt den Versprechungen, die wiederum den Rückruffunktionen folgten.

Verwenden Sie unsere Zeitmaschine, um zur Rückrufhölle zu gelangen [4]:

 function callbackHell() { boilPot( () => { addCarrots(); letPotKeepBoiling(() => { addOnions(); letPotKeepBoiling(() => { console.log("   !"); }, 1000); }, 5000); }, 5000, chopCarrots(), chopOnions() ); } 

Ich werde nicht lügen, ich habe dieses Beispiel spontan geschrieben, als ich an diesem Artikel gearbeitet habe, und es hat viel mehr Zeit gekostet, als ich zugeben möchte. Viele von Ihnen wissen vielleicht nicht einmal, was hier los ist. Mein lieber Freund, sind all diese Rückruffunktionen nicht schrecklich? Lassen Sie es sich zu einer Lektion machen, Callback-Funktionen nie wieder zu verwenden ...

Und wie versprochen das gleiche Beispiel mit expliziten Versprechungen:

 function makeSoup() { return Promise.all([ new Promise((reject, resolve) => { chopCarrots(); chopOnions(); resolve(); }), boilPot() ]) .then(() => { addCarrots(); return letPotKeepBoiling(5); }) .then(() => { addOnions(); return letPotKeepBoiling(10); }) .then(() => { console.log("   !"); }); } 

Wie Sie sehen, ähneln Versprechen immer noch den Rückruffunktionen.
Ich werde nicht auf Details eingehen, aber am wichtigsten:

  • .then ist eine Versprechungsmethode, die das Ergebnis aufnimmt und an die Argumentfunktion (im Wesentlichen an eine Rückruffunktion ...) übergibt.
  • Sie können das Ergebnis eines Versprechens niemals außerhalb des Kontexts von .then . Im Wesentlichen ist .then wie ein asynchroner Block, der ein Ergebnis erwartet und es dann an eine Rückruffunktion übergibt.
  • Neben der .then Methode gibt es eine weitere Methode in .catch - .catch . Es wird benötigt, um Fehler in Versprechungen zu behandeln. Ich werde jedoch nicht auf Details eingehen, da es zu diesem Thema bereits eine Milliarde Artikel und Tutorials gibt.

Fazit


Ich hoffe, Sie haben in diesem Artikel eine Vorstellung von Versprechungen und asynchroner Programmierung bekommen oder vielleicht zumindest ein gutes Beispiel aus dem Leben gelernt, um dies jemand anderem zu erklären.

Also, welchen Weg benutzt du: Versprechen oder asynchron / warten?
Die Antwort liegt ganz bei Ihnen - und ich würde sagen, dass es nicht so schlimm ist, sie zu kombinieren, da beide Ansätze vollständig miteinander kompatibel sind.

Ich persönlich bin jedoch zu 100% im Async / Wait-Camp, da der Code für mich viel verständlicher ist und das wahre Multitasking der asynchronen Programmierung besser widerspiegelt.



[1] : Vollständiger Quellcode ist hier verfügbar.
[2] : Originalartikel „Async-Funktion vs. eine Funktion, die ein Versprechen zurückgibt " , Übersetzung des Artikels " Der Unterschied zwischen einer asynchronen Funktion und einer Funktion, die ein Versprechen zurückgibt . "
[3] : Sie können argumentieren, dass JavaScript wahrscheinlich den asynchronen / erwarteten Typ aus dem Funktionsumfang bestimmen und rekursiv überprüfen kann, aber JavaScript wurde nicht entwickelt, um die Sicherheit statischer Typen beim Kompilieren zu gewährleisten, ganz zu schweigen davon dass es für Entwickler viel bequemer ist, die Art der Funktion explizit zu sehen.
[4] : Ich habe "asynchrone" Funktionen geschrieben, vorausgesetzt, sie funktionieren unter der gleichen Schnittstelle wie setTimeout . Beachten Sie, dass Rückrufe nicht mit Versprechungen kompatibel sind und umgekehrt.

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


All Articles