Node-Seq auf eine neue Art und Weise (wieder über Asynchronität)

Hallo habr Ich schreibe dir als Slopok. Während Raumschiffe mit vertikalem Start- und Landepflug die Weiten der Ozeane befahren und die ungeduldigsten ES6-Funktionen in ihren Projekten verwenden, habe ich Ihnen eine weitere Bibliothek mitgebracht, um den Asynchronen das Leben zu erleichtern.

Natürlich gibt es schon seit langer Zeit eine Million Umsetzungen von Versprechungen und für diejenigen, die es wünschen, asynchron. Es gibt auch eine bekannte Seq- Bibliothek in engen Kreisen des berüchtigten Genossen Substack. Ich habe es fast seit den ersten Tagen meines Javascript verwendet und wo immer ich konnte. Der von dieser Bibliothek vorgeschlagene Ansatz scheint mir verständlicher und logischer zu sein, um asynchrone Nudeln einzudämmen, als der Ansatz, der beispielsweise bei der asynchronen Verarbeitung verwendet wird. Überzeugen Sie sich selbst:

var fs = require('fs');
var Hash = require('hashish');
var Seq = require('seq');
Seq()
  .seq(function () {
    fs.readdir(__dirname, this);
  })
  .flatten()
  .parEach(function (file) {
    fs.stat(__dirname + '/' + file, this.into(file));
  })
  .seq(function () {
    var sizes = Hash.map(this.vars, function (s) { return s.size })
    console.dir(sizes);
  });

Alles ist so einfach und klar, dass es sogar Faulheit erklärt. Leider wurde die Bibliothek schon lange nicht mehr weiterentwickelt und gepflegt. Zusätzlich wurde während der Nutzungszeit eine Liste von Fehlern gesammelt, die mir auf die eine oder andere Weise begegnet sind, eine Liste von Funktionen, die ich wollte, eine Liste von Beschwerden - weil nicht alles so gut funktionierte, wie ich es mir vorgestellt hatte. Wieder einmal stolperte ich über die Unvollkommenheit der Welt und entschied, dass dieser Moment gekommen war. Es ist Zeit, sich zu teilen und zu reparieren. Ich mache das oft, aber was diese Bibliothek macht, schien mir echte Magie.

Und hier bin ich glücklich und entschlossen, Git Clone, CD, Gvim zu machen und zu verstehen, was hier vor sich geht. Verstehe nicht. Der Autor benutzt einige seiner Bibliotheken und zur Erleuchtung müssen Sie sich zuerst mit ihnen befassen. Nach ein paar Stunden langweile ich mich und entdecke einen tödlichen Fehler. Gesagt, getan. Ich setze mich und schreibe die Dream Library von Grund auf neu. Seltsamerweise war in all dem keine Magie. Der Prototyp war bereit für den Abend. Dann habe ich es für einige Zeit fertiggestellt und es bereits in einem realen Projekt verwendet und es vollständig durch Seq ersetzt. Und so stellte sich heraus, was passiert ist. Lasst uns kennenlernen.

YAFF - Noch ein Flow Framework.

Insgesamt habe ich versucht, es mit Seq kompatibel zu machen. Und der größte Teil des Codes wurde einfach durch Ersetzen des Imports migriert (es war var Seq = require ('seq'); es wurde var Seq = require ('yaff');). Natürlich musste etwas anderes ersetzt werden. Seq verwendet die .catch () -Methode, um Flöhe zu fangen. Zum Beispiel kann der obige Code wie folgt geändert werden:

var fs = require('fs');
var Hash = require('hashish');
var Seq = require('seq');
Seq()
  .seq(function () {
    fs.readdir(__dirname, this);
  })
  .flatten()
  .parEach(function (file) {
    fs.stat(__dirname + '/' + file, this.into(file));
  })
  .catch(function (err)(
    console.error(err);
  ))
  .seq(function () {
    var sizes = Hash.map(this.vars, function (s) { return s.size })
    console.dir(sizes);
  });

Diese Konstruktion ist insofern schrecklich, als wir, nachdem wir den Fehler "aufgefangen" haben, weitermachen können. Das ist unmöglich. Erstens ist es nicht klar, was zu tun ist, wenn parEach (oder andere ähnliche Methoden) einige Fehler auslöst. Nur den Ersten fangen? Alles fangen? Und wenn wir schon weit nach unten gegangen sind und in irgendeinem Fall ein Fehler in der Zeitschaltuhr aufgetaucht ist? Und wenn wir unten schon keinen Fang haben? Aber was ist, wenn der unten stehende Haken nicht für die Fehlerbehandlung vorbereitet ist, die für jeden Timer aus forEach hervorgeht? Es gibt viele unbeantwortete Fragen. Daher habe ich beschlossen, dass es in jedem YAFF nur ein Konstrukt für die Fehlerbehandlung geben sollte und es am Ende stehen sollte. Und um die Tradition der Nodejs nicht zu verletzen, lassen Sie es auch das Ergebnis verarbeiten. Es stellt sich heraus, Schönheit. Stellen Sie sicher:

var fs = require('fs');
YAFF(['./', '../'])
  .par(function (path) {
    fs.readdir(path, this);
  })
  .par(function (path) {
    fs.readdir(path, this);
  })
  .flatten()
  .parMap(function (file) {
    fs.stat(__dirname + '/' + file, this);
  })
  .map(function (stat) {
    return stat.size;
  })
  .unflatten()
  .finally(function (e, sizes) {
    if (e)
      throw e;
    log(sizes);
  });

Wenn wir annehmen, dass dies alles in der asynchronen Funktion liegt, können wir in finally einfach den Rückruf auslösen, der uns zur Verfügung gestellt wurde, a la:

var listDirs = function (dirs, cb) {
  YAFF(dirs)
    [волшебные пузырьки]
    .finally(cb);
};

Ist es bequem Ich mag auch. Im Allgemeinen ist diese Bibliothek in Bezug auf das Konzept einer Reihe von Argumenten aufgebaut (es hat sich von Anfang an gelohnt, darüber zu schreiben). Und alle Methoden, die hier so oder so vorhanden sind, ändern diesen Stapel, indem sie auf ihn oder seine einzelnen Elemente angewendet werden. Angenommen, eine in par eingeschlossene Menge von Funktionen nimmt ein Element aus dem Stapel, und zwar in der Reihenfolge, in der diese par geschrieben werden, und erst wenn alle par Callbacks ausgeführt haben (und der Callback in allen Methoden dies ist), geht YAFF zu dem, was als nächstes kommt Warteschlangen. Nehmen wir weiter an, dass wir mehrere Funktionen in der Folge haben. YAFF wendet den gesamten Stapel auf sie an und ersetzt ihn durch etwas, das eine umbrochene Funktion an den Rückruf zurückgibt. Hier ist der Code, um es klar zu machen:

YAFF(['one', 'two', 'three'])
  .par(function (one) {
    this(null, one);
  })
  .par(function (two) {
    this(null, two);
  })
  .par(function (three) {
    this(null, three);
  })
  .seq(function (one, two, three) {
    this(null, one, three);
  })
  .seq(function (one, three) {
    this(null, null);
  })
  .seq(function () {
    this(null, 'and so on and so forth');
  })

Wenn jemand seinen Rückruf mit einem ersten Argument ungleich Null aufruft (Fehler), dann spuckt YAFF natürlich sofort alle Funktionen aus, die noch vorhanden sind, und geht zu dem, was schließlich geschrieben ist. Wenn Sie vergessen, endgültig zu schreiben, oder wenn Sie bedenken, dass Ihr Fehlercode mit Sicherheit nicht YAFF sein kann, wird im Fehlerfall kurzerhand eine Ausnahme ausgelöst. Es ist also besser, dass es endlich so ist.

Es gibt auch alle Arten von synchronen Funktionen, um mit dem Argumentstapel als Array zu arbeiten: Drücken, Ziehen, Filtern, Festlegen, Reduzieren, Reduzieren, Erweitern, Unflatten, Leeren, Verschieben, Nichtverschieben, Verbinden, Umkehren, Speichern (Name), Laden (FromName) ) Fuf, so. Die Namen sprechen für sich, aber wenn das so ist - zögern Sie nicht zu fragen und schauen Sie sich die Quelle an. Es gibt eine Datei ( main.js ) und alles ist sehr einfach.

Nun, natürlich, für das, worum es ging, asynchrone Funktionen zum Verarbeiten von Daten-Arrays: forEach (YAFF wartet nicht, wenn dieser Block verarbeitet wird, die Ergebnisse dieses Blocks wirken sich nicht auf den Stapel aus. YAFF geht sofort zum nächsten Handler in der Kette), seqEach , parEach (YAFF wartet, bis alle Funktionen aufgenommen wurden, die Ergebnisse haben jedoch keinen Einfluss auf den Stapel). seqMap, parMap, seqFilter, parFilter - mach was du erwartest; YAFF wartet, bis sie funktionieren, und die Ergebnisse dieser Blöcke ersetzen diejenigen, die zuvor auf dem Stapel waren. Außerdem können Sie für alle Methoden mit dem Präfix par nach der Funktion eine Zahl angeben. Diese Anzahl ist die Grenze für gleichzeitig arbeitende asynchrone Funktionen. Irgendwie so:

var resizePhotos(photos, cb) {
  YAFF(photos)
    .parMap(function (photo) {
      asyncReisze(photo.image, photo.params, this);
    }, 10)
    .unaflatten()
    .finally(cb);
}

In diesem Beispiel wird die Größe eines Fotostapels asynchron geändert. Um den Server nicht zu überlasten, passen wir für jeden Client nicht mehr als 10 Bilder gleichzeitig an. "Unflatten" ist erforderlich, um über den Stapel verteilte Fotos in einem Array zu sammeln, das ein Argument für einen Rückruf darstellt.

YAFF hat auch die Methoden mseq und mpar - dies ist ein Knicks gegen asynchrone Benutzer. Diese Methoden akzeptieren eine Reihe von Funktionen, die nacheinander oder parallel ausgeführt werden. Mit dem gleichen Erfolg könnten Sie eine Reihe von seq () und par () schreiben, aber manchmal möchten Sie Funktionen dynamisch generieren. Wir haben immer noch eine funktionierende Sprache, oder?

Um Sie völlig zu verwirren, habe ich mir folgendes Beispiel ausgedacht und ein Bild gezeichnet (in der verzweifelten Hoffnung, dass es alles klarer machen würde):

YAFF(['./'])
  .mseq([
    function (path1) {
      fs.readdir(path1, this);
    },
    function (arg) {
      this(null, arg);
    }
  ])
  .flatten()
  .parMap(function (file) {
    fs.stat(__dirname + '/' + file, this);
  })
  .map(function (stat) {
    return stat.size;
  })
  .unflatten()
  .finally(function (e, sizes) {
    log(sizes);
  });

Großes Bild


Ich hoffe, dass ich klar erklären konnte, was ich wollte; Sie sind von der Idee inspiriert und ich bin nicht der einzige, der nicht versteht, warum Async benötigt wird.

Alle spezifischen Kommentare und Vorschläge zu machen , aus besser in Form von Fragen (in englischer Sprache, zögern Sie nicht - ich zu schlecht ihn kenne, wird Literatur trainieren zusammen) auf githabe oder sogar einen Pool rekvest .

Oh ja, die Bibliothek ist bei npmjs.org .

PS Ich habe gerade aus Leidenschaft eine synchrone Apply-Methode hinzugefügt - jetzt können alle anderen synchronen Methoden verworfen werden. Aber ich werde es für die Bequemlichkeit und Kompatibilität verlassen.

Jetzt auch beliebt: