Introduction to Reactive Programming
Tyson Thomas
tysonthomas.90@gmail.com
Events
[{x: 10, y:15}, {x:12, y:18}, {x: 14, y:20}]
....{x: 10, y:15}...{x:12, y:18}...{x: 14, y:20}....
VS
Array
Events and Arrays are both collections
is a Sequence of data over time
Event Stream
Synchronous Array Data is easy to work with
We have all kinds of neat tools for working with synchronous data
-
map
-
reduce
-
filter
-
and so on..
But what about async data?
"Reactive programming is programming with asynchronous data streams."
- Andre Staltz
So what's a stream?
A stream is simply a collection that arrives over time.
Collection
[ 1, 2, 3]
Stream
1
2
3
Observables
Observables are like collections...
Except they arrive over time asynchronously
Observables are like Promises...
Except they work with multiple values.
They clean up after themselves
They can be cancelled
Oh, and they let you map, filter, reduce (and more!)
Observable Example
import {Observable} from './rx'
//Observable constructor
let myObservable = new Observable(observer => {
observer.next(1);
observer.next(2);
observer.next(3);
observer.complete();
});
myObservable.subscribe(
val => console.log(val),
err => console.log(err),
_ => console.log('done')
);
function* values(){
yield 1;
yield 2;
yield 3;
}
let i = values();
i.next() //{value: 1, done: false}
i.next() //{value: 2, done: false}
i.next() //{value: 3, done: false}
i.next() //{done: true}
let myObservable = new Observable(observer => {
observer.next(1);
observer.next(2);
observer.next(3);
observer.complete();
});
myObservable.subscribe(
val => console.log(val),
err => console.log(err),
_ => console.log('done')
);
Pull
Push
Iterator Pattern
Duality
Observer Pattern
So in Observer Pattern..
..Producer Iterates you
Observables can model...
-
DOM Events
-
Timer Intervals
-
Data Requests
-
Animations
Event Observable
import {Observable} from './rx'
const myButton = document.getElementById('myButton');
let clicks$ = new Observable(observer => {
let onClick = ev => observer.next(ev);
myButton.addEventListener('click', onClick);
return () => {
myButton.removeEventListener('click', onClick);
}
});
let clickListener = clicks$.subscribe(ev => console.log(ev));
Inputs - .map
const myInput = document.getElementById('myInput');
//stream of keyup Events
let keyups$ = Observable.fromEvent(myInput, 'keyup');
//map to the values
let inputs$ = keyups$.map(ev => ev.target.value);
inputs$.subscribe(text => console.log(text));
//h
//he
//hel
//hell
//hello
Counter - .reduce/.scan
const incrementButton = document.getElementById('increment');
const decrementButton = document.getElementById('decrement');
const counterOutput = document.getElementById('output');
const getValue = ev => parseInt(ev.target.value,10);
let increments$ = Observable.fromEvent(incrementButton, 'click');
let decrements$ = Observable.fromEvent(decrementButton, 'click');
//merge into a single stream, map to int values
let changes$ = Observable.merge(increments$, decrements$).map(getValue);
//scan (reduce) to track the state
let total$ = changes$.scan((total, value) => total + value, 0);
//set count
total$.subscribe(count => {
counterOutput.innerText = count;
console.log(count);
});
Search - .flatMapLatest
const myInput = document.getElementById('myInput');
// Get all distinct key up events from the input and only fire if long enough and distinct
var keyup = Rx.Observable.fromEvent(myInput, 'keyup')
.map(function (ev) {
return ev.target.value; //get the input value
})
.filter(function (text) {
return text.length > 2; // Only if the text is longer than 2 characters
})
.debounce(750) //Pause for 750ms
.distinctUntilChanged(); // Only if the value has changed
var searcher = keyup.flatMapLatest(searchWikipedia);
// Search Wikipedia for a given term
function searchWikipedia (term) {
return $.ajax({
url: 'http://en.wikipedia.org/w/api.php',
dataType: 'jsonp',
data: {
action: 'opensearch',
format: 'json',
search: term
}
}).promise();
}