Die kürzeste Einführung in die reaktive Programmierung

Der Zweck dieses Artikels ist es zu veranschaulichen, warum reaktive Programmierung erforderlich ist, wie sie sich auf die funktionale Programmierung bezieht und wie sie zum Schreiben von deklarativem Code verwendet wird, der leicht an neue Anforderungen angepasst werden kann. Darüber hinaus möchte ich dies so kurz und einfach wie möglich anhand eines realitätsnahen Beispiels tun.


Nehmen Sie die folgende Aufgabe:
Es gibt einen bestimmten Service mit REST-API und Endpunkt /people . Eine POST-Anforderung an diesen Endpunkt erstellt eine neue Entität. Schreiben Sie eine Funktion, die ein Array von Objekten der Form { name: 'Max' } und erstellen Sie mithilfe der API eine Reihe von Entitäten (auf Englisch wird dies als Stapeloperation bezeichnet).


Lassen Sie uns dieses Problem in einem imperativen Stil lösen:


 const request = require('superagent') function batchCreate(bodies) { const calls = [] for (let body of bodies) { calls.push( request .post('/people') .send(body) .then(r => r.status) ) } return Promise.all(calls) } 

Zum Vergleich schreiben wir diesen Code in einem funktionalen Stil um. Der Einfachheit halber meinen wir mit funktionalem Stil:


  1. Verwendung von funktionalen Grundelementen ( .map , .filter , .reduce ) anstelle von Imperativschleifen ( for , while )
  2. Der Code ist in "reine" Funktionen unterteilt - sie hängen nur von ihren Argumenten ab und nicht vom Status des Systems

Funktionsstilcode:


 const request = require('superagent') function batchCreate(bodies) { const calls = bodies.map(body => request .post('/people') .send(body) .then(r => r.status) ) return Promise.all(calls) } 

Wir haben ein Stück Code der gleichen Größe und es lohnt sich zuzugeben, dass nicht klar ist, wie dieses Stück besser ist als das vorherige.
Um zu verstehen, warum der zweite Code besser ist - Sie müssen mit dem Ändern des Codes beginnen, stellen Sie sich vor, dass für die ursprüngliche Aufgabe eine neue Anforderung aufgetreten ist:
Der von uns aufgerufene Dienst ist auf die Anzahl der Anforderungen in einem bestimmten Zeitraum begrenzt: In einer Sekunde kann ein Client nicht mehr als fünf Anforderungen ausführen. Wenn Sie mehr Anforderungen ausführen, gibt der Dienst einen 429-HTTP-Fehler zurück (zu viele Anforderungen).


An dieser Stelle lohnt es sich wahrscheinlich, anzuhalten und zu versuchen, das Problem selbst zu lösen,% username%


Nehmen wir unseren Funktionscode als Grundlage und versuchen, ihn zu ändern. Das Hauptproblem der "reinen" funktionalen Programmierung besteht darin, dass sie nichts "weiß" - über die Laufzeit und die Eingabe / Ausgabe (auf Englisch gibt es dafür einen Ausdruck für Nebenwirkungen ), aber in der Praxis arbeiten wir ständig mit ihnen.
Um diese Lücke zu schließen, hilft Reactive Programming - eine Reihe von Ansätzen, mit denen versucht wird, das Problem der Nebenwirkungen zu lösen. Die bekannteste Implementierung dieses Paradigmas ist die Rx- Bibliothek, die das Konzept der reaktiven Streams verwendet .


Was sind reaktive Ströme? Wenn nur sehr kurz, dann ist dies ein Ansatz, mit dem Sie funktionale Grundelemente (.map, .filter, .reduce) auf etwas anwenden können, das über die Zeit verteilt ist.


Zum Beispiel senden wir einen bestimmten Befehlssatz über das Netzwerk - wir müssen nicht warten, bis wir den gesamten Satz erhalten, wir präsentieren ihn als reaktiven Stream und können damit arbeiten. Hier ergeben sich zwei weitere wichtige Konzepte:


  • Der Fluss kann unendlich oder willkürlich über die Zeit verteilt sein
  • Die sendende Seite sendet den Befehl nur, wenn die empfangende Seite bereit ist, ihn zu verarbeiten (Gegendruck).

Der Zweck dieses Artikels ist es, einfache Wege zu finden. Daher werden wir die Highland- Bibliothek verwenden, die versucht, das gleiche Problem wie Rx zu lösen, aber viel einfacher zu erlernen ist. Die Idee darin ist einfach: Nehmen wir Node.js Streams als Basis und übertragen Daten von einem Stream zu einem anderen.


Beginnen wir: Beginnen wir mit einem einfachen - wir werden unseren Code "reaktiv" machen, ohne neue Funktionen hinzuzufügen


 const request = require('superagent') const H = require('highland') function batchCreate(bodies) { return H(bodies) .flatMap(body => H(request .post('localhost:3000/people') .send(body) .then(r => r.status) ) ) .collect() .toPromise(Promise) } 

Worauf Sie achten sollten:


  • H (Körper) - Wir erstellen einen Stream aus einem Array
  • .flatMap und der Rückruf, den es akzeptiert. Die Idee ist ganz einfach: Wir verpacken Promise in einen Stream-Konstruktor, um einen Stream mit einem Wert (oder einem Fehler) zu erhalten. Es ist wichtig zu verstehen, dass dies der Wert ist, nicht Promise.
    Dies gibt uns einen Strom von Flüssen - mit flatMap glätten wir ihn in einen einzigen Strom von Werten, mit denen wir arbeiten können (wer hat die Monade gesagt?)
  • .collect - Wir müssen alle Werte in einem "Punkt" in einem Array sammeln
  • .toPromise - wird zu uns zurückkehren Promise, das in dem Moment erfüllt wird, in dem wir einen Wert aus dem Stream haben

Versuchen wir nun, unsere Anforderung umzusetzen:


 const request = require('superagent') const H = require('highland') function batchCreate(bodies) { return H(bodies) .flatMap(body => H(request .post('localhost:3000/people') .send(body) .then(r => r.status) ) ) .ratelimit(5, 1000) .collect() .toPromise(Promise) } 

Dank des Gegendruckkonzepts ist dies nur eine Zeile .Ratelimit in diesem Paradigma. In Rx benötigt es ungefähr den gleichen Speicherplatz .


Nun, das ist alles, Ihre Meinung ist interessant:


  • habe ich es geschafft, das am Anfang des Artikels angegebene Ergebnis zu erzielen?
  • Ist es möglich, mit einem imperativen Ansatz ein ähnliches Ergebnis zu erzielen?
  • Interessieren Sie sich für reaktive Programmierung?

PS: Hier finden Sie einen weiteren Artikel von mir über reaktive Programmierung

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


All Articles