Sergio Xalambrí
Web Developer at Daffy.org
JavaScript FullStack Realtime Frontend Developer at Platzi & Otaku & Dungeon Master & Argentino & Pochoclo lover
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
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 |
buffer('300ms')
map('obtener la cantidad de elementos')
1
2
1
2
filter('mayor a 1')
2
2
RxJava - RxJS - Rx.NET - UniRx - RxScala - RxClojure - RxCpp - Rx.rb - RxPY - RxGroovy - RxJRuby - RxKotlin - RxSwift - RxPHP
RxNetty - RxAndroid - RxCocoa - RxDOM
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
By Sergio Xalambrí
Introducción a Rx.js y la Programación Funcional Asíncrona.