In JS kompilierte Sprache - die eleganteste Art, synchron zu warten

8

Ich versuche (noch eine andere) Sprache zu erstellen, die zu JavaScript kompiliert wird. Eine der Funktionen, die ich haben möchte, ist die Möglichkeit, die asynchronen Operationen von JavaScript synchron auszuführen (nicht genau synchron - natürlich ohne den Hauptthread zu blockieren). Weniger reden, mehr Beispiele:

/* These two snippets should do exactly the same thing */

// the js way
var url = 'file.txt';
fetch(url)
    .then(function (data) {
        data = "(" + data + ")";
        return sendToServer(data);
    }).then(function (response) {
        console.log(response);
    });

// synchronous-like way
var url = 'file.txt';
var response = sendToServer("(" + fetch(url) + ")");
console.log(response);

Was ist die eleganteste Art, es wieder in JS zu kompilieren?

Die Kriterien, sortiert nach Wichtigkeit:

  1. Performance
  2. Abwärtskompatibilität
  3. Lesbarkeit des kompilierten Codes (nicht wirklich wichtig)

Es gibt verschiedene Möglichkeiten, dies umzusetzen. Diese drei kamen mir in den Sinn:

  1. Versprechen verwenden:

    • f(); a( b() ); würde sich in verwandeln
    • f().then(function(){ return b() }).then(function(x){ a(x) });
    • Profis: relativ einfach zu implementieren, polyfillable zurück zu ES3
    • Nachteile: Erstellen einer neuen Funktion und einer Proxy-Instanz für jeden Funktionsaufruf
  2. Generatoren verwenden :

    • f(); a( b() ); würde sich in verwandeln
    • yield f(); tmp = yield b(); yield a( tmp );
    • Profis: schöneres Javascript
    • Nachteile: Erstellen von Proxys, Generatoren und Iteratoren bei jedem Schritt, auch nicht polyfüllbar
    • BEARBEITEN: Tatsächlich können sie mit einem anderen Recompiler (z. B. Regenerator ) mehrfach gefüllt werden.
  3. Verwenden von synchronem XMLHttpRequest:

    • Fordern Sie ein Serverskript an, das auf einen weiteren Aufruf von einer anderen Stelle im Code wartet
    • Profis: de facto keine Änderungen im Code, super einfach zu implementieren
    • Nachteile: erfordert Serverskript (=> keine Portabilität, nur Browser); Darüber hinaus blockiert synchrones XMLHttpRequest den Thread, sodass er überhaupt nicht funktioniert
    • EDIT: Es würde tatsächlich in einem Worker funktionieren

Das Hauptproblem ist, dass man bis zur Laufzeit nicht erkennen kann, ob eine Funktion asynchron ist . Dies führt dazu, dass die beiden Lösungen, die zumindest funktionieren , viel langsamer sind als reines JS. Deshalb frage ich:

Gibt es bessere Möglichkeiten zur Implementierung?

Aus Antworten:

Schlüsselwort abwarten (@ MI3Guy)

  • C # Weg (was gut ist !)
  • führt ein neues Schlüsselwort ein (das als Funktion getarnt werden kann (Bürger erster Klasse), das z. B. ausgelöst wird, wenn der Programmierer versucht, es als Argument zu übergeben).
  • Woher wissen Sie, dass diese Funktion asynchron ist? Keine anderen Schlüsselwörter!
    • Jede Funktion, die ein awaitSchlüsselwort enthält, wird asynchron?
    • Wenn der Code erreicht ist await, gibt er ein Versprechen zurück? Sonst behandelt als eine gewöhnliche Funktion?
m93a
quelle
11
synchronously (without blocking the thread, of course) Du benutzt dieses Wort weiter. Ich denke nicht, dass es bedeutet, was Sie denken, dass es bedeutet, weil synchron "Blockieren des Threads" bedeutet.
Mason Wheeler
2
Lesen Sie mehr über Fortsetzungen und Fortsetzung Passing Style
Basile Starynkevitch
1
@MasonWheeler: Ich gehe davon aus, dass die Operation bedeutet, dass er eine synchrone Syntax verwenden möchte.
Brian
Ich werde dies nicht als Antwort bezeichnen, aber was Sie versuchen, klingt ein bisschen wie das, was C # mit seinen asynchronen Funktionen macht. Ich denke, die konsistenteste Methode mit Rückgabewerten wäre die Verwendung von Versprechungen, und ja, dies würde viele anonyme Funktionen beinhalten. Sie könnten versuchen, Artikel darüber zu finden, wie C # -Code als Referenz transkompiliert wird. Es ist wahrscheinlich in gewisser Hinsicht genauso ineffizient.
Katana314
2
f(); a( b() );würde sich in f().then(b).then(a);Sie nicht Funktionen einschließen.
thefourtheye

Antworten:

3

Angenommen, die Sprache wird dynamisch eingegeben, kann ich zwei verschiedene Möglichkeiten sehen, wie dies behandelt werden kann, ohne zu wissen, ob ein Aufruf asynchron ist.

Ein Ansatz wäre anzunehmen, dass jeder Anruf asynchron sein könnte. Dies wäre jedoch unglaublich ineffizient und würde viel zusätzlichen Code erzeugen. Dies ist im Wesentlichen das, was Sie in Option 2 tun.

Eine andere Möglichkeit ist die Verwendung von Fasern. Fasern sind wie leichte Threads, die vom Programm geplant werden. Die Faser selbst entscheidet, wann sie die Kontrolle aufgibt. Diese erfordern jedoch Betriebssystemunterstützung. Soweit ich das beurteilen kann, besteht die einzige Möglichkeit, Fasern in JavaScript zu verwenden, in node.js. Ich vermute, wenn Sie zu JS kompilieren, besteht das Ziel (oder zumindest ein Ziel) darin, im Browser ausgeführt zu werden, was diese Option ausschließt. Dies wäre eine leichtere Version von Option 3.

Ehrlich gesagt würde ich in Betracht ziehen, ein Schlüsselwort wie das von C # hinzuzufügen await. Dies hat neben der Angabe, dass eine Funktion asynchron ist, eine Reihe von Vorteilen. Funktionen können aufgerufen werden, um Futures zu produzieren, ohne sie sofort abzuwarten. Auf diese Weise können mehrere asynchrone Vorgänge gleichzeitig und nicht nacheinander ausgeführt werden. Darüber hinaus ist es besser, explizit anzugeben, welche Vorgänge asynchron sind, da anderer Code ausgeführt werden kann, während der Vorgang ausgeführt wird. Wenn die Asynchronisierung automatisch erfolgt, kann es überraschend sein, wenn einige von Ihnen verwendete Daten durch einen externen Code geändert werden.

In C # innerhalb einer als asynchron gekennzeichneten Methode wird die return-Anweisung verwendet, um den Wert zurückzugeben, der sich in der Zukunft befindet, nicht in der Zukunft selbst. Beachten Sie jedoch, dass der Async-Modifikator nicht wirklich Teil der Methodensignatur ist. awaitkann mit jeder Methode verwendet werden, die eine Zukunft zurückgibt.

Wenn Sie keinen asynchronen Modifikator für Funktionen hinzufügen möchten, können Sie (wie angegeben) entscheiden, dass jede Funktion mit einer Wartezeit so behandelt wird, als hätte sie einen impliziten asynchronen Modifikator. Selbst wenn eine Funktion eine hat await, aber vor Erreichen zurückkehrt, sollte die Funktion dennoch eine Zukunft zurückgeben. Ich würde auch empfehlen, eine Möglichkeit aufzuzeigen, um einen Wert in eine abgeschlossene Zukunft zu konvertieren, die den Wert enthält, insbesondere wenn es keine Möglichkeit gibt, dies implizit durch Hinzufügen des asynchronen Modifikators zu tun.

MI3Guy
quelle
Mein ursprüngliches Ziel war es, eine völlig konsistente Sprache ohne Strukturen zu erstellen, die nicht wiederverwendet werden konnten (z. B. if(){} else{}könnte der Programmierer auch eine ähnliche Struktur wie diese definieren ifZero(){} elseWhile(){}). Es scheint jedoch, dass ich eine globale Wartefunktion einführen muss, die auslöst (oder zumindest undefiniert zurückgibt), wenn der Programmierer versucht, ihren Wert einer Variablen zuzuweisen (Funktionen sind Bürger erster Klasse).
M93a
Die Frage wurde aktualisiert (ganz unten). Würden Sie mir bitte sagen, was Sie über die dortigen Fragen denken? Vielen Dank.
M93a
Ich habe basierend auf Ihren Updates weitere Details hinzugefügt. Lassen Sie mich wissen, ob Sie etwas anderes gemeint haben.
MI3Guy