rxjs swetugg
TRANSCRIPT
Why Rxjs?We want to deal with async in a “synchronous looking way”
We want something better than promises
We want one paradigm for async to rule them all
Promises to the rescue
service.getData()
.then(getMoreData) .then(getEvenMore) .then(andSomeMore)
Looks great right?
But promises were flawedNo cancellation
Can’t deal with other async concepts like mouse positions, clicks, user input
No rich composition
And brexit happened
Cumbersome to retryOnly returns one value
What is an observableObservable is just a function
that takes an observer and returns a functionObserver: an object with next, error, complete methods
Rx.Observable.create((observer) => { observer.next(1); observer.error(‘error’); observer.complete();})
1 2 3 4 5 6 7
stream of value over time
Promise vs Array
vs Observable
list.map( x = > x.prop ).filter( x => x > 2 ).take( 2 )
Arraylist.map( x = > x.prop ).filter( x => x > 2 ).take( 2 ).subscribe(
x => console.log(x), err => console.log(err)
)
Observable
Promiseservice.get().then( x => console.log(x) ).catch( err => console.log(err) ) but can also
- Cancelled- Retried
Array like, handles async
var stream = Rx.Observable.create((observer) =>{ })})
Emits
stream .subscribe( (data) => { console.log( data ); })
1
next()
observer.next(1);
2
next()
observer.next(2);
3
next()
observer.next(3);
var stream = Rx.Observable.create((observer) =>{
})
stream .subscribe( (data) => { console.log( data ); } (err) => { console.log(err); })
Emits 1
next()
observer.next(1);
error message
error()
observer.error(‘something went wrong’)
var stream = Rx.Observable.create((observer) =>{ })})stream .subscribe( (data) => { console.log( data ); } (err) => { console.log(err) }, () => { console.log(‘completed’) })
Emits 1
next()
observer.next(1);
complete()
observer.complete();
var homemadeStream = Rx.Observable.create((observer) => { var i=0;
});
var subscription2 = homemadeStream.subscribe((val) => { console.log('Homemade val',val);});
setTimeout(() => { console.log('Cancelling homemadeStream'); subscription2.unsubscribe();}, 1500); Calling dispose
Produce values till someone calls unsubscribevar handle = setInterval(() => {
observer.next( i++ ); }, 500);
Define whats to happen on unsubscribe
return function(){ console.log('Disposing timeout'); clearTimeout( handle ); }
Rx.Observable.fromArray([ 1,2,3,4 ])
Rx.Observable.fromEvent(element, ‘event’);Rx.Observable.fromArray(eventEmitter, ‘data’, function(){})
Rx.Observable.fromNodeCallback(fs.createFile)Rx.Observable.fromCallback(obj.callback)Rx.Observable.fromPromise(promise)
Rx.Observable.fromIterable(function *() { yield 20 })
Rx.Observable.range(1,3)Rx.Observable.interval(miliseconds)
var stream = Rx.Observable.create((observer) => { var request = new XMLHttpRequest();
request.open( ‘GET’, ‘url’ ); request.onload =() =>{ if(request.status === 200) { } else { } } request.onerror = () => { } request.send();})
stream.subscribe( )
observer.next( request.response );
(result) => { console.log( result ); }
Get our dataobserver.complete();
() => { console.log(‘completed’); }
No more data, close stream
observer.error( new Error( request.statusText ) )
(err) => { console.log(err) },
observer.error( new Error(‘unknown error’) );
Error
Error
Observables are cold by default, unless you make them hot
var stream = Rx.Observable.interval(1000);
stream.subscribe((val) => { console.log('Subscriber 1', val);});
stream.subscribe((val) => { console.log('Subscriber 2', val); });
var stream = Rx.Observable .interval(1000) .publish();
stream.subscribe((val) => { console.log('Subscriber 1', val);});
setTimeout(function() { stream.connect(); }, 2000);
setTimeout(() => { stream.subscribe((val) => { console.log('Value', val); });}, 4000);
Subscriber 2 Values 0 1 2 3 4
Subscriber 1 Values 0 1 2 3 4
0 1 2 3 4 5 6
3 4 5 6
You can create an observable from almost any
async conceptOperators however gives
it its power
Remember:
But:
120+ operators Rxjs 460+ Rxjs 5
Combination Conditional
Multicasting Filtering
Transformation Utility
Categories
in production
Operator
Most operators are covered at rxmarbles.com
Stream 1 2 3
Other stream 4 5
Resulting stream 1 2 3 4 5
Operator examplevar stream = Rx.Observable.of(1,2,3,4,5);stream
stream.subscribe((data) => { console.log(‘data’); })
Operators :map()filter()
3Emits
6
.map((val) => { return val + 1;})
changes the value
.filter((val) => { return val % 3 === 0;})
filters out values
Dovar stream = Rx.Observable.of(1,2,3,4,5);
var subscription = stream
.filter(function(val){ return val % 2 === 0;});
subscription.subscribe(function(val){ console.log('Val',val);})
Echos every value without changing it,
used for logging.do((val) => { console.log('Current val', val);})
Current val 1Current val 2Current val 3Current val 4Current val 5Subscribe:
24
DelayDelay,the whole sequence
stream
.subscribe( (val) => { var newTime = new Date().getTime(); console.log('Delayed', val + " " + (newTime - time));})
Rx.Observable.merge(
).subscribe((val) => { var newTime = new Date().getTime(); console.log('Merged Delay', val + " " + (newTime - time));})
….. 1 second 123
….. 1 second Marco
….. 2 secondPolo
.delay(1000)
Rx.Observable.of('Marco').delay(1000), Rx.Observable.of('Polo').delay(2000)
combineLatestvar stream = Rx.Observable.fromArray([1,2,3,4]);
var stream2 = Rx.Observable.fromArray([5,6,7,8]);
var combineLatest = Rx.Observable
combineLatest.subscribe( function(val) { console.log( 'combine ', val ) }) [1,5], [2,6], [3,7], [4,8]
.combineLatest( stream, stream2 );
debounce
var debounceTime = Rx.Observable.fromEvent(button,'click')
debounceTime.subscribe( function(){ console.log('mouse pressed');})
waits x ms and returns latest emitted
Ignores all generated mouse click events
for 2 seconds.debounce(2000);
Clicking save button2secclick click click click click
save()
takeUntil
.subscribe(
(err) => {},
)
var mouseUp = Rx.Observable.fromEvent(button,'mouseup');Break condition
.takeUntil( mouseUp )
() =>{ console.log('condition fulfilled'); }
Generate numbersvar mousePressedTimer = Rx.Observable.interval(1000);
triggers every secondmousePressedTimer
(val) =>{ console.log('mouse up has NOT happened yet’,val); },
var throttle = Rx.Observable .interval( 1000 ) .throttle( 1000 );
throttle.subscribe( (val) => { console.log('Throttle', val );});
throttleDelay every value by x milliseconds
1 2 3 4
1 2
bufferlet numbers = Rx.Observable.interval(1000);
let bufferBy = Rx.Observable.fromEvent(document,'click');
let buffered = numbers.buffer( bufferBy );
buffered.subscribe( );
Numbers are generated to a buffer until condition is met, then the buffer content is emitted
Generate value to buffer
Release all values
(values) => { console.log(‘Buffered',values);}
switchMapSwitch map,
complete something based on a condition
breakCondition = Rx.Observable.fromEvent(document,'click');breakCondition.switchMap((val) => { return Rx.Observable.interval(3000).mapTo(‘Do this');})
breakCondition.subscribe((val) => { console.log('Switch map', val);})
Intended action is completed/restarted by ‘breakCondition’
etc..
Do thisDo thisDo this
Do thisDo this
click
click
source.subscribe((data) => { console.log( data );})
flatMaplet source = Rx.DOM.getJSON( 'data2.json' )
return Rx.Observable.fromArray( data ).map((row) => { return row.props.name; }); return observable
.flatMap((data) => {
} );
We get an array response that we want to emit row by row
We use flatMap instead of map because :We want to flatten our list to one stream
flatMap explainedwhen you create a list of observables flatMap flattens that list so it becomes one stream
Great when changing from one type of stream to another
Without it you would have to listen to every single substream, would be messy
event
event
event
event
ajax ajax ajax ajax
json json json json
flatMap
map
Problem : Autocomplete
Listen for keyboard pressesFilter so we only do server trip after x number of chars are enteredDo ajax call based on filtered input
Cash responses, don’t do unnecessary calls to http server
let input = $(‘#input’);input.bind(‘keyup’,() = >{ let val = input.val() if(val.length >= 3 ) { if( isCached( val ) ) { buildList( getFromCache(val) ); return; }
doAjax( val ).then( (response) => { buildList( response.json() ) storeInCache( val, response.json() ) }); }})
fetch if x characters long
return if cached
do ajax
Ok solution but NOT so fluentWe need 3 methods to deal with cache
Stream modeling
key
key
key
key
key
key
FILTER
AJAX CALL
json
json
MAP
key
key
key
key
key
key
key
response
response
flatmapExample = Rx.Observable.fromEvent(input,'keyup')
flatmapExample.subscribe( (result) =>{ console.log('Flatmap', result); buildList( result ) })
more fluent
Transform event to char.map((ev) => { return ev.target.value;})
Wait until we have 3 chars.filter(function(text){ return text.length >=3;})
Only perform search if this ‘search’ is unique.distinctUntilChanged()Excellent to use when coming from one stream to another
.switchMap((val) => { return Rx.DOM.getJSON( 'data3.json' );})
Error handlingkills the stream
var errStream = Rx.Observable.throw('Error');
var stream = Rx.Observable.create((observer) => { observer.next(1); })
var merged = Rx.Observable .merge( errStream, stream )
merged.subscribe( (val) => { console.log('Val', val);}, (err) => { console.log('Err', err);}, () => { console.log('completed');})
Captured here but sequence interrupted,completed NOT reached
Error handlingbetter, but can be improved
var errStream = Rx.Observable.throw('Error');
var stream = Rx.Observable.create((observer) => { observer.onNext(1); })
var merged = Rx.Observable.merge( errStream, stream )
merged.subscribe( (val) => { console.log('Val', val); }, (err) => { console.log('Err', err); },
)
stream not processed thoughcan we improve it?
Completed reach, stream neveremits its values though () => { console.log(‘completed'); }
.catch((err) => { return Rx.Observable.of(err);});
Error handlingwrap error stream before
mergevar errStream = Rx.Observable.throw('AAA thump..').catch(function(err){ return Rx.Observable.of(err);});
merged.subscribe( (val) => { console.log('Val', val); }, (err) => { console.log('Err', err); },
) () => { console.log(‘completed'); }
all values are emitted
Errorignoring
You have several sources and two terminatesYou want the other normal source to work
var errStream = Rx.Observable.throw('AAA thump..');
var errStreamWithFlattenedData = Rx.Observable.interval(500).take(3).flatMap((val) => { if( val === 1 ) { return Rx.Observable.throw('crashing'); } else { return Rx.Observable.return(val); }})
var normalStream = Rx.Observable.return('anything');
var handledErrorStream = Rx.Observable .onErrorResumeNext( errStream, normalStream, errStreamWithFlattenedData );handledErrorStream.subscribe( (val) =>{ console.log('error stream ignored', val);}, (err) => { console.log("error stream ignored, error",err);}, () => { console.log("completion of error stream ignored”);})
Error handlingretry
let stream = Rx.Observable.interval(1000)
.take(6);
.map((n) => { if(n === 2) { throw 'ex'; } return n;})
Produce error
.retry(2)Number of tries
before hitting error callback
stream.subscribe((data) => console.log(data)(error) => console.log(error)
1Emits
3
Makes x attempts before error cb is called
Error handlingretryWhen, delay between
attemptslet stream = Rx.Observable.interval(1000)
.take(6);
delay, 200 ms .retryWhen((errors) => { return errors.delay(200); })
.map((n) => { if(n === 2) { throw 'ex'; } return n;})
produce an error when= 2
stream.subscribe((data) => console.log(data)(error) => console.log(error)
What about schedulers and testing?
Because scheduler has its own virtual clockAnything scheduled on that scheduler will adhere to time denoted on the clock
I.e we can bend time for ex unit testing
Schedulerstesting
var testScheduler = new Rx.TestScheduler();
var stream = Rx.Observable.interval(1000, testScheduler).take(5).map((val) => { return val + 1}).filter((i) => { return i % 2 === 0});
var result;stream.subscribe((val) => result = val );
console.log('testing function’);
testScheduler.advanceBy(1000);testScheduler.advanceBy(1000);console.log('Should equal', result === 4);
increment operatortestScheduler.advanceBy(1000);testScheduler.advanceBy(1000);testScheduler.advanceBy(1000);
assertconsole.log('Should equal', result === 2);
.then vs .subscribe
getData().then( )
getData().subscribe( )
I will keep on streaming values
(data) => console.log(data),(data) => console.log(data),
(err) => console.log(err) (err) => console.log(err)
Cascading callsResponse:
//getUser
stream
.subscribe((orderItem) => { console.log('OrderItem',orderItem.id);})
{ id: 11, userId : 1 }.then(getOrderByUser)
.switchMap((user) => { //getOrder return Rx.Observable.of({ id : 11, userId : user.id }).delay(3000) })
{ id: 123, orderId : 11 }.then(getOrderItemByOrder)
.switchMap((order) => { //getOrderItem return Rx.Observable.of({ id: 114, orderId: order.id })})
{ id: 1 }getUser()
var stream = Rx.Observable.of({ id : 1 });
So we can see the first user observable being dropped when user 2 is emitted
Short word on switchMap
This is to ensure we throw away the other calls when a new user is emitted
We don’t wantgetUsergetOrderByUsergetOrderItemByOrder
to complete if a new user is emitted
1 2 3
2 4 5
Not continuedReplaces above
stream
Cascading callwait for the first
.subscribe( (data) => { console.log( 'orders', data[0] ); console.log( 'messages', data[0] ); })
var stream = Rx.Observable.of([{ id : 1 }, { id : 2 }]);
getUser()We wait for user
function getOrdersAndMessages(user){return Promise.all([ getOrdersByUser( user.id ), getMessagesByUser( user.id )])
}
.then(getOrdersAndMessages)
stream.switchMap((user) => { return Rx.Observable.forkJoin( Rx.Observable.of([ { id: 1, userId : user.id } ]).delay(500), // orders Rx.Observable.of([ { id: 100, userId : user.id } ]).delay(1500) //messages )})
Calls to orders and message can happen in parallel
Orders,Messagesarrive at the same time