Flujos de datos con Rx.js

Sergio Daniel Xalambrí

JavaScript FullStack Realtime Frontend Developer at Platzi & Otaku & Dungeon Master & Argentino & Pochoclo lover

Programación Funcional Reactiva

Programación Funcional

Programación Reactiva

Es un paradigma de programación orientado a los flujo de los datos y la propagación de cambios

Uniendo la PF y la PR podemos trabajar sobre flujos de datos dinámicos de forma funcional usando funciones puras

Es un paradigma de programación centrado en el uso de funciones en vez de los cambios de estado

Pull vs Push

En un sistema Pull el Consumer determina cuando recibir datos del Producer

En un sistema Push el Producer determina cuando enviar datos al Consumer

Producer Consumer
Pull ​Pasivo: genera datos cuando se lo piden. Activo: decide cuando consumir datos.
Push Activo: produce datos a su propio ritmo. Pasivo: Reacciona a los datos que recibe.
Un dato Múltiples datos
Sync Funciones Generadores
Async Async/Await Observables

Tipos de producers

Flujo de datos

buffer('300ms')

map('obtener la cantidad de elementos')

1

2

1

2

filter('mayor a 1')

2

2

ReactiveX

RxJava - RxJS - Rx.NET - UniRx - RxScala - RxClojure - RxCpp - Rx.rb - RxPY - RxGroovy - RxJRuby - RxKotlin - RxSwift - RxPHP

Implementaciones

Lenguajes

Plataformas y Frameworks

RxNetty - RxAndroid - RxCocoa - RxDOM

Ejemplos de código

import Rx from 'rx';

const stream$ = Rx.Observable.create(observer => observer.next(42));

stream$.subscribe(value => console.log(value));
stream$.subscribe(value => console.log(value));
  

Ejemplo básico

import Rx from 'rx';

const $button = document.querySelector('button');

const click$ = Rx.Observable.fromEvent($button, 'click');

click$
  .bufferWithTime(300)
  .map(clicks => clicks.length)
  .filter(length => length > 1)
  .subscribe(
    clicks => console.log(`se hicieron ${clicks} clicks en menos de 300ms.`)
  );

Escuchando eventos del DOM

import Rx from 'rx-dom';

const $query = document.getElementById('searchquery');

const query$ = Rx.Observable.fromEvent($query, 'change');

query$
  .map(event => event.target.value) // obtenemos el valor
  .filter(value => value.length > 0) // eliminamos los valores vacíos
  .debounce(300) // hacemos un debounce de 300ms
  .map(value => `https://jsonplaceholder.typicode.com/posts/${value}`) // armamos la url
  .flatMap(url => Rx.DOM.getJSON(url)) // hacemos el request AJAX
  .subscribe(::console.log, ::console.error); // mostramos el resultado en consola

Caja de búsqueda

// importamos las dependencias
import Rx from 'rx';
import { Map as map } from 'immutable';

// obtenemos los elementos del DOM
const $increment = document.querySelector('#increment');
const $decrement = document.querySelector('#decrement');
const $amount = document.querySelector('#amount');

// creamos nuestro dispatcher
const dispatcher$ = new Rx.Subject();

// definimos nuestro reducer
function reducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state.update('count', 1, v => v + 1);
    case 'DECREMENT':
      return state.update('count', 0, v => v - 1);
    default:
      return state;
  }
}

// definimos el estado inicial
const initialState = map({ count: 0 });

// creamos el store con el estado inicial a partir del dispatcher y usando el reducer
const store$ = dispatcher$
  .startWith(initialState)
  .scan(reducer);

// hacemos un debounce de 300ms y nos suscribimos a los cambios de estado
store$.debounce(300).subscribe(state => {
  // escribimos el nuevo valor en el amount
  $amount.setAttribute('value', state.get('count'));
});

// creamos un observable del evento click y lo mapeamos a acciones de tipo INCREMENT
const increment$ = Rx.Observable
  .fromEvent($increment, 'click')
  .map(() => ({ type: 'INCREMENT' }));

// creamos un observable del evento click y lo mapeamos a acciones de tipo DECREMENT
const decrement$ = Rx.Observable
  .fromEvent($decrement, 'click')
  .map(() => ({ type: 'DECREMENT' }));

// combinamos lo dos observables anteriores
const actions$ = Rx.Observable
  .merge(increment$, decrement$);

// nos suscribimos y despachamos las acciones que llegan
actions$.subscribe(action => dispatcher$.onNext(action));

Simil-Redux

import { Observable, Subject } from 'rx';
import eio from 'engine.io-client';

function webSocket(url = '') {
  if (url === '') throw new TypeError('The URL cannot be empty.');
  let currentSocket;

  const socket$ = new Subject();
  const messages$ = new Subject();
  const close$ = new Subject();

  close$
    .startWith(250)
    .scan(timeout => {
      if (timeout < 5000) return timeout * 2;
      return timeout;
    })
    .subscribe(() => socket$.onNext(eio(url)));

  socket$.subscribe(socket => {
    currentSocket = socket;
    socket.on('close', () => close$.onNext());
    socket.on('message', data => messages$.onNext(data));
  });

  return {
    on(type = '') {
      if (type === '') return messages$;
      return messages$
        .filter(message => message.type === type);
    },
    send(message) {
      currentSocket.send(
        typeof message === 'string' ? message : JSON.stringify(message)
      );
    },
  };
}

export default webSocket;

Cliente de WebSockets

import Rx from 'rx-dom';
import marked from 'marked';

const filesInput = document.querySelector('input');
const text = document.querySelector('#text');

Rx.DOM.change(filesInput)
  .flatMap(function (event) {
    return Rx.Observable.from(event.target.files);
  })
  .filter(function (file) {
    return file.type.match('text/markdown');
  })
  .flatMap(function (file) {
    return Rx.DOM.fromReader(file).asText();
  })
  .map(marked)
  .subscribe(function (content) {
    text.innerHTML = contents;
  });

Leer archivos Markdown y transformarlos

import Rx from 'rx-dom';

const filesInput = document.querySelector('input');
const text = document.querySelector('#text');

Rx.DOM.change(filesInput)
  .flatMap(function (event) {
    return Rx.Observable.from(event.target.files);
  })
  .flatMap(function (file) {
    return Rx.DOM.fromReader(file).asText();
  })
  .subscribe(function (contents) {
    text.innerHTML = `<pre><code>${contents}</code></pre>`;
  });

Leer código y mostrarlo

¿Preguntas?

Flujos de datos con Rx.js

By Sergio Xalambrí

Flujos de datos con Rx.js

Introducción a Rx.js y la Programación Funcional Asíncrona.

  • 1,255