La introducci贸n m谩s corta a la programaci贸n reactiva

El prop贸sito de este art铆culo es ilustrar por qu茅 se necesita programaci贸n reactiva, c贸mo se relaciona con la programaci贸n funcional y c贸mo usarla para escribir c贸digo declarativo que sea f谩cil de adaptar a los nuevos requisitos. Adem谩s, quiero hacer esto de la manera m谩s breve y simple posible con un ejemplo cercano a lo real.


Toma la siguiente tarea:
Hay un cierto servicio con REST API y endpoint /people . Una solicitud POST a este punto final crea una nueva entidad. Escriba una funci贸n que tome una matriz de objetos de la forma { name: 'Max' } y cree un conjunto de entidades utilizando la API (en ingl茅s, esto se denomina operaci贸n por lotes).


Resolvamos este problema en un estilo imperativo:


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

A modo de comparaci贸n, reescribamos este c贸digo en un estilo funcional. Por simplicidad, entendemos por estilo funcional:


  1. Uso de primitivas funcionales ( .map , .filter , .reduce ) en lugar de bucles imperativos ( para , while )
  2. El c贸digo est谩 organizado en funciones "puras": dependen solo de sus argumentos y no dependen del estado del sistema

C贸digo de estilo funcional:


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

Obtuvimos un c贸digo del mismo tama帽o y vale la pena confesar que no est谩 claro c贸mo esta pieza es mejor que la anterior.
Para comprender por qu茅 la segunda parte del c贸digo es mejor: debe comenzar a cambiar el c贸digo, imagine que ha aparecido un nuevo requisito para la tarea original:
El servicio que llamamos tiene un l铆mite en el n煤mero de solicitudes en un per铆odo de tiempo: en un segundo, un cliente no puede ejecutar m谩s de cinco solicitudes. La ejecuci贸n de m谩s solicitudes har谩 que el servicio devuelva un error HTTP 429 (demasiadas solicitudes).


En este punto, probablemente valga la pena detenerse e intentar resolver el problema usted mismo,% username%


Tomemos nuestro c贸digo funcional como base e intentemos cambiarlo. El principal problema de la programaci贸n funcional "pura" es que no "sabe" nada sobre el tiempo de ejecuci贸n y la entrada-salida (en ingl茅s hay una expresi贸n para los efectos secundarios de esto), pero en la pr谩ctica trabajamos constantemente con ellos.
Para llenar este vac铆o, la programaci贸n reactiva viene al rescate: un conjunto de enfoques que intentan resolver el problema de los efectos secundarios. La implementaci贸n m谩s famosa de este paradigma es la biblioteca Rx que utiliza el concepto de flujos reactivos .


驴Qu茅 son las corrientes reactivas? Si es muy breve, entonces este es un enfoque que le permite aplicar primitivas funcionales (.map, .filter, .reduce) a algo distribuido en el tiempo.


Por ejemplo, enviamos un cierto conjunto de comandos a trav茅s de la red; no necesitamos esperar hasta obtener todo el conjunto, lo presentamos como un flujo reactivo y podemos trabajar con 茅l. Aqu铆 surgen otros dos conceptos importantes:


  • el flujo puede ser infinito o distribuido arbitrariamente en el tiempo
  • el lado emisor transmite el comando solo si el lado receptor est谩 listo para procesarlo (contrapresi贸n)

El prop贸sito de este art铆culo es encontrar formas f谩ciles, por lo tanto, tomaremos la biblioteca Highland , que intenta resolver el mismo problema que Rx, pero es mucho m谩s f谩cil de aprender. La idea interna es simple: tomemos las secuencias de Node.js como base y "transfiramos" datos de una secuencia a otra.


Comencemos: comencemos con uno simple: haremos que nuestro c贸digo sea "reactivo" sin agregar nuevas funcionalidades


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

A qu茅 debe prestar atenci贸n:


  • H (cuerpos): creamos una secuencia a partir de una matriz
  • .flatMap y la devoluci贸n de llamada que acepta. La idea es bastante simple: envolvemos Promise en un constructor de flujo para obtener un flujo con un valor (o un error. Es importante entender que este es el valor, no Promise).
    Como resultado, esto nos da un flujo de flujos: usando flatMap lo suavizamos en un solo flujo de valores en los que podemos operar (驴qui茅n dijo la m贸nada?)
  • .collect - necesitamos recolectar todos los valores en un "punto" en una matriz
  • .toPromise - nos devolver谩 la Promesa, que se cumplir谩 en el momento en que tengamos un valor de la transmisi贸n

Ahora intentemos implementar nuestro requisito:


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

Gracias al concepto de contrapresi贸n, esta es solo una l铆nea .ratelimit en este paradigma. En Rx, se necesita aproximadamente la misma cantidad de espacio .


Bueno, eso es todo, tu opini贸n es interesante:


  • 驴Logr茅 lograr el resultado declarado al comienzo del art铆culo?
  • 驴Es posible lograr un resultado similar utilizando un enfoque imperativo?
  • 驴Est谩s interesado en la programaci贸n reactiva?

PD: aqu铆 puedes encontrar otro art铆culo m铆o sobre programaci贸n reactiva

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


All Articles