L'introduction la plus courte à la programmation réactive

Le but de cet article est d'illustrer pourquoi la programmation réactive est nécessaire, comment elle est liée à la programmation fonctionnelle et comment l'utiliser pour écrire du code déclaratif facile à adapter aux nouvelles exigences. De plus, je veux le faire aussi brièvement et simplement que possible par un exemple proche du réel.


Effectuez la tâche suivante:
Il existe un certain service avec REST API et endpoint /people . Une demande POST à ​​ce point de terminaison crée une nouvelle entité. Écrivez une fonction qui prend un tableau d'objets de la forme { name: 'Max' } et créez un ensemble d'entités à l'aide de l'API (en anglais, cela s'appelle une opération par lots).


Résolvons ce problème dans un style impératif:


 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) } 

À titre de comparaison, réécrivons ce morceau de code dans un style fonctionnel. Par simplicité, nous entendons par style fonctionnel:


  1. Utilisation de primitives fonctionnelles ( .map , .filter , .reduce ) au lieu de boucles impératives ( for , while )
  2. Le code est organisé en fonctions "pures" - elles ne dépendent que de leurs arguments et ne dépendent pas de l'état du système

Code de style fonctionnel:


 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) } 

Nous avons obtenu un morceau de code de la même taille et il convient d'avouer qu'il n'est pas clair en quoi ce morceau est meilleur que le précédent.
Afin de comprendre pourquoi le deuxième morceau de code est meilleur - vous devez commencer à changer le code, imaginez qu'une nouvelle exigence est apparue pour la tâche d'origine:
Le service que nous appelons a une limite sur le nombre de demandes dans une période de temps: en une seconde, un client ne peut pas exécuter plus de cinq demandes. L'exécution de plusieurs demandes entraînera le service à renvoyer une erreur HTTP 429 (trop de demandes).


À ce stade, cela vaut probablement la peine d'arrêter et d'essayer de résoudre le problème vous-même,% username%


Prenons notre code fonctionnel comme base et essayons de le changer. Le principal problème de la programmation fonctionnelle "pure" est qu'elle ne "sait" rien - à propos de l'exécution et de l'entrée-sortie (en anglais, il y a une expression pour les effets secondaires pour cela), mais dans la pratique, nous travaillons constamment avec eux.
Pour combler cette lacune, la programmation réactive vient à la rescousse - un ensemble d'approches essayant de résoudre le problème des effets secondaires. L'implémentation la plus célèbre de ce paradigme est la bibliothèque Rx utilisant le concept de flux réactifs .


Que sont les flux réactifs? Si c'est très brièvement, alors c'est une approche qui vous permet d'appliquer des primitives fonctionnelles (.map, .filter, .reduce) à quelque chose distribué dans le temps.


Par exemple, nous envoyons un certain ensemble de commandes sur le réseau - nous n'avons pas besoin d'attendre d'avoir l'ensemble complet, nous le présentons comme un flux réactif et nous pouvons travailler avec. Deux autres concepts importants se posent ici:


  • le flux peut être infini ou distribué arbitrairement dans le temps
  • le côté émetteur ne transmet la commande que si le côté récepteur est prêt à la traiter (contre-pression)

Le but de cet article est de trouver des moyens simples, par conséquent, nous prendrons la bibliothèque Highland , qui essaie de résoudre le même problème que Rx, mais est beaucoup plus facile à apprendre. L'idée à l'intérieur est simple: prenons les flux Node.js comme base et «transférons» les données d'un flux à un autre.


Commençons: commençons par un simple - nous rendrons notre code "réactif" sans ajouter de nouvelles fonctionnalités


 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) } 

À quoi devez-vous faire attention:


  • H (corps) - nous créons un flux à partir d'un tableau
  • .flatMap et le rappel qu'il accepte. L'idée est assez simple - nous enveloppons Promise dans un constructeur de flux pour obtenir un flux avec une seule valeur (ou une erreur. Il est important de comprendre que c'est la valeur, pas Promise).
    En conséquence, cela nous donne un flux de flux - en utilisant flatMap, nous le lissons en un seul flux de valeurs sur lequel nous pouvons opérer (qui a dit la monade?)
  • .collect - nous devons collecter toutes les valeurs dans un "point" dans un tableau
  • .toPromise - nous renverra la promesse, qui sera remplie au moment où nous aurons une valeur du flux

Essayons maintenant de mettre en œuvre notre exigence:


 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) } 

Grâce au concept de contre-pression, ce n'est qu'une ligne .ratelimit dans ce paradigme. Dans Rx, cela prend environ la même quantité d'espace .


Et bien c'est tout, votre avis est intéressant:


  • ai-je réussi à atteindre le résultat déclaré au début de l'article?
  • Est-il possible d'obtenir un résultat similaire en utilisant une approche impérative?
  • Êtes-vous intéressé par la programmation réactive?

PS: ici vous pouvez trouver un autre de mes articles sur la programmation réactive

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


All Articles