Rxjs ngvikings
-
Upload
christoffer-noring -
Category
Technology
-
view
145 -
download
0
Transcript of Rxjs ngvikings
Rxjs everything is a stream
Christoffer NoringGoogle Developer Expert
@chris_noring
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
Once upon a time in async land…There were callbacks
Callbacks turned into callback hell
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
Help us ObservablesYou’re my only hope
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
So pretty much linq with async
Rx.Observable.create( fnValue,, fnError,, fnCompleted )
Observable signature
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();
Cancelling.unsubscribe()
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 ); }
You will always create an observable from something
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)
Wrap an observable
next()error()
complete()
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
Hot vs Cold Observable
Cold Observablerecorded tv show
Hot observableLive streaming
eg World Cup Final
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:
Operatorsmakes your code look like linq
120+ operators Rxjs 460+ Rxjs 5
Combination Conditional
Multicasting Filtering
Transformation Utility
Categories
in production
Marble diagramhow does that operator work
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
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()
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
The procedural approach
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
The observable approach
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 handlingwhen streams fail
errorcompletion
.catch() completion
completion
no values
completion
values, WIN!
.catch()
merge
.catch()merge
onErrorResumeNext
completionvalues, WIN!
errorcompletion
no values
retrylet 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
retryWhendelay between attempts
let 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)for those shaky connections
What did we learn so far?
We can cancel with .unsubsribe()We can retry easily
A stream generates a continuous stream of valuesOperators manipulate either the values or the stream/s
We can “patch” an erronous stream with a .catch()orIgnore a failing stream altogether with onErrorResumeNext
Schedulersbending time
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);
Comparing promises to Rxjs
.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)
user
order
orderItem
Fetch user
Then fetch order
Lastly fetch order item
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
user
orders messages
Fetch user
Fetch in parallell
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
Last summaryWe can use schedulers to easily test our code
Cascading calls can easily be setup
switchMap over flatMap when doing ajax callsbecause we need it to abandon the stream if
the first condition change
Rxjs An elegant weapon for a
more civilized age
Remember..
Thank you