rxjs swetugg

66
Rxjs Christoffer Noring Google Developer Expert @chris_noring

Upload: christoffer-noring

Post on 12-Feb-2017

128 views

Category:

Technology


0 download

TRANSCRIPT

RxjsChristoffer Noring

Google 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

Everything is a stream(s)

and we can bend it to our will

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();

Disposingcleaning up after us

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

Operatorsutility

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)

Operatorscombination

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 );

Operatorsfiltering

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

Operatorstransformation

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

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

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)

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)

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

Rxjs An elegant weapon for a

more civilized age

Remember..

Thank you