rethink async with rxjs
TRANSCRIPT
Reactive Programming
What if?
The iterator pattern and observer met at a bar, !fell in love, got married, and had a baby?
Reactive Programming
What if?
This baby would allow you to use the same code to !respond to events and asynchronous operations?
Observables
Tracking Mouse Movements
1s 2s
Mousedown
0s
Mouseup
{x : 200, y: 521} {x : 240, y: 552} {x : 210, y: 602} {x : 199, y: 579}{x : 221, y: 530} {x : 218, y: 570} {x : 200, y: 599}
Observables
var mouseMoves = Observable.fromEvent( mouseDown ) .takeUntil( mouseUp ) .map( function( mouseEvent ){ return { x : mouseEvent.clientX, y : mouseEvent.clientY }; } ) ; !mouseMoves.subscribe( function( cords ){ //do stuff for each mouse move cord });
Mouse Moves Observable
Observables
Observables Everywhere!• Events!• AJAX Requests!• Node Functions!• Arrays!• Promises!• And more….
Observables
Why not another Async approach?
• Replayable!• Composable!• Cancellable!• Cache-able!• Shareable!• Can emit multiple value-able
var searchResultsSets = keyups. map( function( key ){ return Observable.getJSON('/search?' + input.value) });
Functional Review
Map - Transforms data
var searchResultsSets = keyups. filter( function ( key ){ return input.value.length > 1; }). map( function( key ){ return Observable.getJSON('/search?' + input.value) );
Functional Review
Filter - Narrows Collections
var html = searchResultsSets. reduce(function( prev,curr ) { return prev + '<li>' + curr.name + '</li>'; },'' );
Functional Review
Reduce - Turns a collection into a single value
Initial prev value.
Functional Review
Zip - Combines two collections
var movies = [ 'Super Troopers', 'Pulp Fiction', 'Fargo' ] ; var boxArts =[ '/cdn/23212/120x80', '/cdn/73212/120x80','/cdn/99212/120x80' ] ; !var withArt = Observable. zip(movies, boxarts, function(movie, boxart){ return {title : movie, boxart : boxart}; }); !//[{title : 'Super Troo…', boxart : '/cdn…'}, // {title : 'Pulp Fict…', boxart : '/cdn…' }, // {title : 'Fargo', boxart : 'cdn…' } ]
Functional Review
Observable Data Streams Are Like Crazy Straws
keyPresses Observable
subscribe
throttlefilter
mapdistinctUntilChanged
reduce
Thinking Functionally
Replace loops with map.
searchResults = Observable. getJSON(‘/people?’ + input.value); !searchResults. forEach( function( reps ){ var names = []; for( var i = 1; i < resp; i++ ){ var name = data[i].fName + ' ' + data[i].lName; names.push( name ); } });
Thinking Functionally
Replace loops with map and reduce.
searchResults = Observable. getJSON(‘/people?’ + input.value). map( function( data ){ return data.fName + data.lName; } ); !searchResults.forEach( function( resp ){ //resp will now be the names array })
Thinking Functionally
Replace if’s with filters.
var keyPresses = O.fromEvent( el, 'keyup' ) !keyPresses.forEach( function( e ){ if( e.which === keys.enter ){ //=> no! //do something } });
Thinking Functionally
Replace if’s with filters.
var enterPresses = O.fromEvent( el, 'keyup' ) .filter( function( e ){ return e.which && e.which === keys.enter; }); !enterPresses.forEach( function( e ){ //do something });
Thinking Functionally
Don’t put too much in a single stream.
var submits = O.fromEvent(input,'keypresses'). throttle(). map( function( e ){ return e.which } ). filter( function( key ){ return key === keys.enter || keys.escape; }). map(). reduce(). ...
Smaller streams are OK.
Thinking Functionally
var keys = Observable.fromEvent(input,'keypresses'). throttle(). map( function( e ){ return e.which } ); !var enters = keys.filter( function( key ) ){ return e.which === keys.enter; } !var escapes = keys.filter( function( key ) ){
Don’t put too much in a single stream.Smaller streams are OK.
Observables = Events Over Time
Key presses over time…
.5s 1s 1.5s 2s 2.5s 3s
B R E A K IJ <- N G B A D
Observables = Events Over Time
1s 2s 3s 4s 5s 6s
BR
BRE
BREAK
BREAKJ
BREAKING
BREAKING BA
BREAKING BAD
Ajax requests over time…
Flattening Patterns
merge - combines items in a collection as each item arrives
concat - combines collections in the order they arrived
switchLatest - switches to the latest collection that ! arrives
Concat
1s 2shttp://jsbin.com/fejod/4/edit
!
!
data
data
data
data
Animated Autocomplete
SearchBBrBreaking Bad Bridezillas Brothers & Sisters The Breakfast Club Brother Bear
Breaking Bad The Breakfast Club Breakout Kings Breaking Dawn Breaking Amish
BreBreaBreak
Observables = Events Over Time
Simple Widget, High Complexity• Respond to key presses
• Send off Ajax requests
• Animate out when search results become invalid
• Animate in when new search results come in
• Don’t show old results
• Make sure one animation is finished before starting another
var searchResultsSets = keyups. filter( function ( e ){ return input.value.length > 1; }). map( function( e ){ return Observable. getJSON('/search?' + input.value); }). switchLatest();
Animated Autocomplete
var animateOuts = keyups. map(function( resultSet ){ return animateOut(resultsDiv); }); !var animateIns = searchResultsSets. map( function( resultSet ){ return Observable. of(resultsSet). concat(animateIn(resultsDiv)); });
Animated Autocomplete
var resultSets = animateOuts. merge(animateIns). concatAll(); !resultSets. forEach( function( resultSet ){ if (resultSet.length === 0) { $('.search-results').addClass('hidden'); } else { resultsDiv.innerHTML = toHTML(resultSet ); } } );
Animated Autocomplete
var keyups = Observable. fromEvent( searchInput, ‘keypress'); !var searchResultsSets = keyups. filter( function ( e ){ return input.value.length > 1; }). map( function( e ){ return Observable. getJSON('/search?' + input.value); }). switchLatest(); var animateOuts = keyups. map(function( resultSet ){ return animateOut(resultsDiv); }); !var animateIns = searchResultsSets. map( function( resultSet ){ return Observable. of(resultsSet). concat(animateIn(resultsDiv)); }); !var resultSets = animateOuts. merge(animateIns). concatAll(); !resultSets. forEach( function( resultSet ){ if (resultSet.length === 0) { $('.search-results').addClass('hidden'); } else { resultsDiv.innerHTML = toHTML(resultSet ); } } );
Animated Autocomplete
In Conclusion
• Observables are a POWERFUL abstraction!• Requires a bit of mental rewiring!• The RX API is HUGE, take baby steps!• Merging strategies are the key coordinating async!• Compose streams of data from small streams
To Find Out More
• http://github.com/jhusain/learnrx!• https://github.com/Reactive-Extensions/RxJS/tree/master/doc!• Chat with me