Wäre es von Vorteil, synchronen Code mit der Syntax von Versprechen zu schreiben?

8

Gibt es ein solches Konzept als synchrones Versprechen? Wäre es von Vorteil, synchronen Code mit der Syntax von Versprechen zu schreiben?

%Vor%

... könnte etwas geschrieben werden (aber mit einer synchronen Version von then );

%Vor%     
Ben 09.03.2015, 08:31
quelle

2 Antworten

17
  

Gibt es ein solches Konzept als synchrones Versprechen?

Benjamin hat absolut Recht. Versprechen sind eine Art von Monade . Sie sind jedoch nicht die einzige Art.

Wenn Sie sich dessen noch nicht bewusst sind, fragen Sie sich wahrscheinlich, was eine Monade ist. Es gibt viele Erklärungen von Monaden online verfügbar. Die meisten von ihnen leiden jedoch unter dem Problem mit dem Monad-Tutorial .

Kurz gesagt ist der Trugschluss, dass die meisten Menschen, die Monaden verstehen, nicht wirklich wissen, wie sie anderen das Konzept erklären sollen. Einfach ausgedrückt sind Monaden ein abstraktes Konzept, und Menschen finden es schwierig, abstrakte Konzepte zu erfassen. Es ist jedoch einfach für Menschen, konkrete Konzepte zu finden.

Lasst uns also unsere Eroberung beginnen, um Monaden zu verstehen, die mit einem konkreten Konzept beginnen. Wie gesagt, Monaden sind ein abstraktes Konzept. Das bedeutet, dass eine Monade eine Schnittstelle ohne eine Implementierung (dh es definiert bestimmte Operationen und gibt an, was diese Operationen tun sollen, ohne zu spezifizieren, wie es gemacht werden muss).

Nun gibt es verschiedene Arten von Monaden. Jede Art von Monade ist konkret (dh sie definiert eine Implementierung der Monade Schnittstelle ). Versprechen sind eine Art von Monade. Daher sind Versprechen ein konkretes Beispiel für eine Monade. Wenn wir also Versprechungen studieren, können wir anfangen, Monaden zu verstehen.

Also, wo fangen wir an? Glücklicherweise hat uns der Benutzer spike einen guten Startpunkt in seinem Kommentar zu Ihrer Frage:

  

Eine Möglichkeit, die ich mir vorstellen kann, ist die Verkettung von Versprechen zusammen mit dem Sync-Code. Während Sie eine Antwort auf diese Frage finden: AJAX-Anfrage dynamisch basierend auf Szenario generieren habe ich einen synchronen Anruf in ein Versprechen eingeschlossen, um in der Lage sein, sie mit anderen Versprechen zu verbinden.

Schauen wir uns seinen Code an:

%Vor%

Hier gibt die Funktion run ein Versprechen zurück, das sich aus den Versprechen zusammensetzt, die von getScenario , mapToInstruction , waitForTimeout , callApi , handleResults und run selbst verkettet zurückgegeben werden.

Nun, bevor wir fortfahren, möchte ich Ihnen eine neue Notation vorstellen, um zu visualisieren, was diese Funktionen tun:

%Vor%

Also hier ist die Aufschlüsselung:

  1. Das Symbol% ​​co_de% bedeutet "ist vom Typ" und das :: Symbol bedeutet "zu" . Zum Beispiel liest -> als " run :: Unit -> Deferred a ist vom Typ run bis Unit " .
  2. Das bedeutet, dass Deferred a eine Funktion ist, die einen run -Wert (d. h. keine Argumente) annimmt und einen Wert vom Typ Unit .
  3. zurückgibt
  4. Hier bedeutet Deferred a einen beliebigen Typ. Wir wissen nicht, welcher Typ a ist und es ist uns egal, welcher Typ a ist. Daher kann es jede Art sein.
  5. Hier ist a ein Dateityp für Versprechen (mit einem anderen Namen) und Deferred bedeutet, dass beim Lösen der Zusage ein Wert vom Typ Deferred a .
  6. zurückgegeben wird

Es gibt verschiedene Dinge, die wir aus der obigen Visualisierung lernen können:

  1. Jede Funktion hat einen gewissen Wert und gibt ein Versprechen zurück.
  2. Der aufgelöste Wert, der von jedem Versprechen zurückgegeben wird, wird zur Eingabe für die nächste Funktion:

    %Vor%
  3. Die nächste Funktion kann erst ausgeführt werden, wenn das vorherige Versprechen aufgelöst wurde, da es den aufgelösten Wert des vorherigen Versprechens verwenden muss.

Nun, wie ich bereits erwähnt habe, ist eine Monade eine Schnittstelle , die bestimmte Operationen definiert. Eine der Operationen, die die Monad-Schnittstelle bereitstellt, ist die Verkettung von Monaden. Bei Versprechungen ist dies die Methode a . Zum Beispiel:

%Vor%

Wir wissen das:

%Vor%

Daher:

%Vor%

Das wissen wir auch:

%Vor%

Daraus können wir folgern:

%Vor%

In Worten ist " then eine Funktion, die zwei Argumente (einen Wert vom Typ then und eine Funktion vom Typ Deferred a ) akzeptiert und einen Wert vom Typ a -> Deferred b zurückgibt." Daher:

%Vor%

Also haben wir unsere erste Monadenoperation:

%Vor%

Diese Operation ist jedoch konkret. Es ist spezifisch für Versprechen. Wir wollen eine abstrakte Operation, die für jede Monade funktionieren kann. Daher verallgemeinern wir die Funktion so, dass sie für jede Monade funktionieren kann:

%Vor%

Beachten Sie, dass diese Funktion Deferred b nichts mit bind . Diese Funktion Function.prototype.bind ist eine Verallgemeinerung der Funktion bind . Dann ist then Funktion Die Funktion then ist jedoch generisch und kann für jede Monade bind verwendet werden.

Der Fettpfeil m bedeutet begrenzte Quantifizierung . Wenn => und a von einem beliebigen Typ sein können, dann kann b von jedem beliebigen Typ sein, was die monad-Schnittstelle implementiert. Es ist uns egal, welcher Typ m ist, solange er die Monad-Schnittstelle implementiert.

So würden wir die Funktion m in JavaScript implementieren und verwenden:

%Vor%

Wie ist das generisch? Nun, ich könnte einen neuen Datentyp erstellen, der die Funktion bind implementiert:

%Vor%

Anstelle von then hätte ich einfach bind(one, isOdd) schreiben können (was eigentlich viel einfacher zu lesen ist).

Der Datentyp one.then(isOdd) ist wie Versprechen eine Art Monade. In der Tat ist es die einfachste aller Monaden. Es heißt Identity , weil es nichts zu seinem Eingabetyp macht. Es behält es so wie es ist.

Verschiedene Monaden haben verschiedene Effekte, die sie nützlich machen. Zum Beispiel haben Versprechungen den Effekt, Asynchronität zu verwalten. Die Identity Monade hat jedoch keinen Effekt. Es ist ein vanilla -Datentyp.

Wie auch immer, wir haben ... eine Operation von Monaden entdeckt, die Funktion Identity . Es gibt noch eine weitere Operation, die noch entdeckt werden muss. In der Tat sprach der Benutzer spike in seinem oben erwähnten Kommentar darauf hin:

  

Ich habe einen synchronen Aufruf in ein Versprechen eingeschlossen, um sie mit anderen Versprechen verbinden zu können.

Sie sehen, das Problem ist, dass das zweite Argument der Funktion bind eine Funktion sein muss, die ein Versprechen zurückgibt:

%Vor%

Dies bedeutet, dass das zweite Argument asynchron sein muss (da es eine Zusage zurückgibt). Manchmal möchten wir jedoch eine synchrone Funktion mit then verketten. Um dies zu tun, wickeln wir den Rückgabewert der synchronen Funktion in ein Versprechen ein. Zum Beispiel ist dies der Spike :

%Vor%

Wie Sie sehen, ist der Rückgabewert der then -Funktion mapToInstruction . Wir müssen es jedoch in ein Zusicherungsobjekt einfügen, weshalb wir dies tun:

%Vor%

Tatsächlich macht er dasselbe auch in der Funktion instruction :

%Vor%

Es wäre schön, diese drei Zeilen in eine separate Funktion zu setzen, damit wir uns nicht wiederholen müssen:

%Vor%

Mit dieser Funktion handleResults können wir unit und mapToInstruction wie folgt neu schreiben:

%Vor%

Tatsächlich ist die Funktion handleResults die zweite fehlende Operation der Monad-Schnittstelle. Wenn es verallgemeinert wird, kann es wie folgt visualisiert werden:

%Vor%

Es wird nur ein Wert in einen Monad-Datentyp geschrieben. Dadurch können Sie regelmäßige Werte und Funktionen in einen monadischen Kontext heben. Zum Beispiel bieten Versprechen einen asynchronen Kontext und unit ermöglicht es Ihnen, synchrone Funktionen in diesen asynchronen Kontext zu heben. In ähnlicher Weise bieten andere Monaden andere Effekte.

Wenn Sie unit mit einer Funktion zusammenstellen, können Sie die Funktion in einen monadischen Kontext bringen. Betrachten wir zum Beispiel die Funktion unit , die wir zuvor definiert haben:

%Vor%

Es wäre schöner (wenn auch langsamer), es stattdessen wie folgt zu definieren:

%Vor%

Es würde noch schöner aussehen, wenn wir eine Funktion isOdd verwenden würden:

%Vor%

Ich habe bereits erwähnt, dass eine Monade eine Schnittstelle ohne Implementierung (dh es definiert bestimmte Operationen und gibt an, was diese Operationen tun sollen, ohne anzugeben, wie dies geschehen soll). Daher ist eine Monade eine Schnittstelle, die:

  1. Definiert bestimmte Operationen.
  2. Gibt an, was diese Operationen tun sollen.

Wir wissen jetzt, dass die zwei Operationen einer Monade sind:

%Vor%

Nun schauen wir uns an, was diese Operationen tun sollten oder wie sie sich verhalten sollten (d. h. wir schauen uns die Gesetze an, die eine Monade regeln):

%Vor%

Bei einem gegebenen Datentyp können wir compose und then Funktionen definieren, die gegen diese Gesetze verstoßen. In diesem Fall sind diese speziellen Implementierungen von unit und then falsch.

Zum Beispiel sind Arrays eine Art von Monade, die nicht-deterministische Berechnungen darstellen. Definieren wir eine falsche unit -Funktion für Arrays (die Funktion unit für Arrays ist korrekt):

%Vor%

Diese falsche Definition von bind für Arrays widerspricht dem zweiten Gesetz (richtige Identität):

%Vor%

Die korrekte Definition von unit für Arrays wäre:

%Vor%

Eine interessante Eigenschaft ist, dass das Array unit function in bind und concat implementiert wurde. Arrays sind jedoch nicht die einzigen Monaden, die diese Eigenschaft besitzen.Jede monad map Funktion kann in Form von verallgemeinerten monadischen Versionen von bind und concat implementiert werden:

%Vor%

Wenn Sie verwirrt darüber sind, was ein Funktor ist, dann machen Sie sich keine Sorgen. Ein Funktor ist nur ein Datentyp, der die Funktion map implementiert. Per Definition ist jede Monade auch ein Funktor.

Ich werde nicht auf die Details der Monadengesetze eingehen und wie fmap und fmap zusammen äquivalent zu join sind. Sie können darüber auf der Wikipedia-Seite nachlesen.

Eine Randnotiz: Laut der JavaScript-Fantasie-Land-Spezifikation heißt die Funktion bind unit und Die Funktion of heißt bind . Dies würde Ihnen erlauben, Code wie zu schreiben:

%Vor%

Wie auch immer, zurück zu Ihrer Hauptfrage:

  

Wäre es von Vorteil, synchronen Code mit der Syntax von Versprechen zu schreiben?

Ja, es gibt große Vorteile, wenn man synchronen Code unter Verwendung der Syntax von Versprechen (d. h. monadischen Code) schreibt. Viele Datentypen sind Monaden und mit der Monad-Schnittstelle können Sie verschiedene Arten von sequentiellen Berechnungen wie asynchrone Berechnungen, nicht-deterministische Berechnungen, Berechnungen mit Fehler, Berechnungen mit Zustand, Berechnungen mit Protokollierung usw. modellieren. Eines meiner Lieblingsbeispiele für die Verwendung von Monaden ist freie Monaden verwenden, um Sprachinterpreter zu erstellen .

Monaden sind ein Merkmal funktionaler Programmiersprachen. Die Verwendung von Monaden fördert die Wiederverwendung von Code. In diesem Sinne ist es definitiv gut. Es kommt jedoch zu einer Strafe. Der Funktionscode ist um Größenordnungen langsamer als der Verfahrenscode. Wenn das kein Problem für Sie ist, sollten Sie definitiv monadischen Code schreiben.

Einige der beliebtesten Monaden sind Arrays (für nicht-deterministische Berechnungen), die chain monad (für Berechnungen, die fehlschlagen können, ähnlich wie Maybe in Gleitkommazahlen) und monadische Parserkombinatoren .

%Vor%      

... könnte etwas geschrieben werden (aber mit einer synchronen Version von NaN );

%Vor%

Ja, Sie können diesen Code definitiv schreiben. Beachten Sie, dass ich nichts über die Methode then erwähnt habe. Der Grund ist, dass Sie keine spezielle Methode fail benötigen.

Beispiel: Erstellen Sie eine Monade für Berechnungen, die fehlschlagen können:

%Vor%

Dann definieren wir fail , foo , bar und bam :

%Vor%

Schließlich können wir es wie folgt verwenden:

%Vor%

Die handleError monad, die ich beschrieben habe, ist tatsächlich die CanFail monad in funktionalen Programmiersprachen. Hoffe das hilft.

    
Aadit M Shah 09.03.2015, 17:00
quelle
1

Die folgende Antwort ist meine Reaktion auf die riesige Antwort von @AaditMShah, die mit einem fetten Kopfgeld belohnt wurde. Während seine Länge und Gründlichkeit mich beeindruckt hat, hat seine Fähigkeit, die Frage sucintly zu beantworten, nicht. Also, auf die Chance, dass ich nicht der einzige bin, hier geht ...

Promises API ist eine Instanz des Monadenmusters aus der funktionalen Programmierung, die es ermöglicht, die funktionale Zusammensetzung mit einem Aspekt zu kombinieren der Rückgabewerte der Funktionen. Seine Syntax spiegelt dieses Muster wider; Die asynchrone Ausführung wird nur in ihrer Implementierung behandelt. Daher ist die Erkennung des Musters eine Antwort an sich.

Um ein wenig zu erweitern, möchten Sie vielleicht das Muster immer dann verwenden, wenn Sie den Prozess der funktionalen Zusammensetzung nutzen möchten. Der Code in Ihrer Frage ist kein gutes Beispiel, da abgesehen von der Ausnahmebehandlung keine Verbindung zwischen den Funktionen besteht. (Natürlich können Sie das Muster für die benutzerdefinierte Fehlerbehandlung verwenden, wenn dies Ihr ursprüngliches Anliegen war.)

Betrachten Sie stattdessen den folgenden Code.

%Vor%

Wir könnten seine funktionale Zusammensetzung erschließen, indem wir es als

umschreiben %Vor%

( _.partialRight ist nur eine Argumentbindung.)

In diesem Fall könnte die neue Syntax sehr nützlich sein, da sie den Datenfluss zwischen den Funktionen abfängt. Je nach Ihren Anforderungen könnten Sie on / do implementieren, um z. parallele Verarbeitung von komplexen Datenstrukturknoten oder Nachgeben nach Blockierung für einige Zeit.

Wie jedes andere Muster führt auch dieses zu einem Overhead (sowohl bei der Effizienz als auch bei der Codepflege) und sollte nur verwendet werden, wenn es einen Grund dafür gibt.

    
hon2a 17.03.2015 15:35
quelle

Tags und Links