rxjs ngvikings

58
Rxjs everything is a stream Christoffer Noring Google Developer Expert @chris_noring

Upload: christoffer-noring

Post on 21-Feb-2017

157 views

Category:

Technology


0 download

TRANSCRIPT

Page 1: Rxjs ngvikings

Rxjs everything is a stream

Christoffer NoringGoogle Developer Expert

@chris_noring

Page 2: Rxjs ngvikings

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

Page 3: Rxjs ngvikings

Once upon a time in async land…There were callbacks

Callbacks turned into callback hell

Page 4: Rxjs ngvikings

Promises to the rescue

service.getData()

.then(getMoreData) .then(getEvenMore) .then(andSomeMore)

Looks great right?

Page 5: Rxjs ngvikings

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

Page 6: Rxjs ngvikings

Help us ObservablesYou’re my only hope

Page 7: Rxjs ngvikings

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

Page 8: Rxjs ngvikings

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

Page 9: Rxjs ngvikings

So pretty much linq with async

Page 10: Rxjs ngvikings

Rx.Observable.create( fnValue,, fnError,, fnCompleted )

Observable signature

Page 11: Rxjs ngvikings

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

Page 12: Rxjs ngvikings

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

Page 13: Rxjs ngvikings

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

Page 14: Rxjs ngvikings

Cancelling.unsubscribe()

Page 15: Rxjs ngvikings

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

Page 16: Rxjs ngvikings

You will always create an observable from something

Page 17: Rxjs ngvikings

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)

Page 18: Rxjs ngvikings

Wrap an observable

next()error()

complete()

Page 19: Rxjs ngvikings

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

Page 20: Rxjs ngvikings

Hot vs Cold Observable

Page 21: Rxjs ngvikings

Cold Observablerecorded tv show

Hot observableLive streaming

eg World Cup Final

Page 22: Rxjs ngvikings

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

Page 23: Rxjs ngvikings

You can create an observable from almost any

async conceptOperators however gives

it its power

Remember:

But:

Page 24: Rxjs ngvikings

Operatorsmakes your code look like linq

Page 25: Rxjs ngvikings

120+ operators Rxjs 460+ Rxjs 5

Combination Conditional

Multicasting Filtering

Transformation Utility

Categories

in production

Page 26: Rxjs ngvikings

Marble diagramhow does that operator work

Page 27: Rxjs ngvikings

Operator

Most operators are covered at rxmarbles.com

Stream 1 2 3

Other stream 4 5

Resulting stream 1 2 3 4 5

Page 28: Rxjs ngvikings

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

Page 29: Rxjs ngvikings

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

Page 30: Rxjs ngvikings

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

Page 31: Rxjs ngvikings

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

Page 32: Rxjs ngvikings

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

Page 33: Rxjs ngvikings

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

Page 34: Rxjs ngvikings

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

Page 35: Rxjs ngvikings

The procedural approach

Page 36: Rxjs ngvikings

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

Page 37: Rxjs ngvikings

The observable approach

Page 38: Rxjs ngvikings

Stream modeling

key

key

key

key

key

key

FILTER

AJAX CALL

json

json

MAP

key

key

key

key

key

key

key

response

response

Page 39: Rxjs ngvikings

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

Page 40: Rxjs ngvikings

Error handlingwhen streams fail

Page 41: Rxjs ngvikings

errorcompletion

.catch() completion

completion

no values

completion

values, WIN!

.catch()

merge

.catch()merge

Page 42: Rxjs ngvikings

onErrorResumeNext

completionvalues, WIN!

errorcompletion

no values

Page 43: Rxjs ngvikings

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

Page 44: Rxjs ngvikings

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

Page 45: Rxjs ngvikings

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

Page 46: Rxjs ngvikings

Schedulersbending time

Page 47: Rxjs ngvikings

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

Page 48: Rxjs ngvikings

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

Page 49: Rxjs ngvikings

Comparing promises to Rxjs

Page 50: Rxjs ngvikings

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

Page 51: Rxjs ngvikings

user

order

orderItem

Fetch user

Then fetch order

Lastly fetch order item

Page 52: Rxjs ngvikings

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

Page 53: Rxjs ngvikings

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

Page 54: Rxjs ngvikings

user

orders messages

Fetch user

Fetch in parallell

Page 55: Rxjs ngvikings

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

Page 56: Rxjs ngvikings

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

Page 57: Rxjs ngvikings

Rxjs An elegant weapon for a

more civilized age

Remember..

Page 58: Rxjs ngvikings

Thank you