Observables -

hot or not?

@ktrz__  |      /ktrz

07.02.2019, meet.js Warszawa

 

Chris Trześniewski

Senior Frontend Developer @Scalac

What's an Observable

 

  • Promise on steroids
  • Stream - data delivered over time           
  • Function that tie observer to a producer

How does it look like?

 

  • Function
  • Accepts an observer: An object with `next`, `error` and `complete` methods on it.
  • Returns cancellation function

How does it look like?

function myObservable(observer) {
    







}
    datasource.ondata = (e) => observer.next(e);
    datasource.onerror = (err) => observer.error(err);
    datasource.oncomplete = () => observer.complete();
    const datasource = new DataSource();
    return () => {
        datasource.destroy();
    };

Enough with the theory

 

How can we use it?

How do we make coffee?

 

  • Get water
  • Heat water
  • Add beans
  • Add milk (optionally)

We can code that!

 

  • callbacks
  • promises
  • observables

Using callbacks

function heatWater(cb) {
    setTimeout(() => cb('hot water'), 1000)
}
function addBeans(cb) {
    setTimeout(() => cb('black coffee'), 1000)
}
function addMilk(cb) {
    setTimeout(() => cb('white coffee'), 1000)
}
function ready(item) {
    console.log(item, 'ready')
}

let coffee
const makeCoffee = () => heatWater(hotWater => {
    ready(hotWater)
    addBeans(blackCoffee => {
        ready(blackCoffee)
        addMilk(whiteCoffee => {
            ready(whiteCoffee)
            coffee = whiteCoffee
        })
    })
})

makeCoffee()

Using promises

function heatWater() {
    return new Promise(resolve => setTimeout(() =>
        resolve('hot water'), 1000))
}
function addBeans() {
    return new Promise(resolve => setTimeout(() =>
        resolve('black coffee'), 1000))
}
function addMilk() {
    return new Promise(resolve => setTimeout(() =>
        resolve('white coffee'), 1000))
}
function ready(item) {
    console.log(item, 'ready')
}
const makeCoffee = () => heatWater()
    .then(hotWater => {
        ready(hotWater)
        return addBeans()
    })
    .then(blackCoffee => {
        ready(blackCoffee)
        return addMilk()
    })
    .then(whiteCoffee => {
        ready(whiteCoffee)
        return whiteCoffee
    })

makeCoffee()
    .then(ready)

Using observables

const createDelayedObservable = v => of(v).pipe(delay(1000))
const heatWater = () => createDelayedObservable('hot water')
const addBeans = () => createDelayedObservable('black coffee')
const addMilk = () => createDelayedObservable('white coffee')

function ready(item) {
    console.log(item, 'ready')
}
const makeCoffee = () => heatWater()
    .pipe(
        tap(ready),
        switchMap(() => addBeans()),
        tap(ready),
        switchMap(() => addMilk()),
        tap(ready)
    )

makeCoffee()
    .subscribe(ready)

Ok,

so we can use Observables like Promises

 

But why?

Can we benefit more?

 

Yes, we can!

Where do Observables shine?

 

  • Working with multiple data delivered over time
  • Lazy evaluation
  • Operators
    • filter, map ...
    • switchMap, mergeMap ...
    • merge, combineLatest ...
    • delay ...

Let come back to...

Order board

@Component({
    /* ... */
    template: `
        <button (click)="clicks$.next($event)">
          Add order
        </button>
        <app-coffee-items [items]="state$ | async">
        </app-coffee-items>
    `
})
export class AppComponent {
  clicks$: Subject<Event> = new Subject();

  state$: Observable<CoffeeRequest[]> = of();
}
  clicks$: Subject<Event> = new Subject();
  coffeeReqs$: Observable<CoffeeRequest> =
    this.clicks$.pipe(
      



    );

  state$: Observable<CoffeeRequest[]> =
    this.coffeeReqs$.pipe(
      





    );
      map(idGenerator()), // generates unique id
      map(createCoffeeRequest), 
      // maps to {id, status: requested}
      scan((
        state: CoffeeRequest[],
        val: CoffeeRequest) => [...state, val],
        []),
      startWith([])

  coffeeMaking$: Observable<CoffeeRequest> =
    this.coffeeReqs$.pipe(assignBarista());
 
  state$: Observable<CoffeeRequest[]> =
    merge(this.coffeeReqs$, this.coffeeMaking$).pipe(
      scan((
        state: CoffeeRequest[],
        val: CoffeeRequest) => [...state, val],
        []),
      startWith([])
    );
}
export class AppComponent {
  coffeeReqs$: Observable<CoffeeRequest> =
    prevCoffeeRequest$.pipe(share());

  coffeeMaking$: Observable<CoffeeRequest> =
    this.coffeeReqs$.pipe(
      delay(1000),
      setStatus(CoffeeRequestStatusValue.making)
    );
    merge(this.coffeeReqs$, this.coffeeMaking$).pipe(
 
  state$: Observable<CoffeeRequest[]> =
    merge(this.coffeeReqs$, this.coffeeMaking$).pipe(
      scan((
        state: CoffeeRequest[],
        val: CoffeeRequest) => [...state, val],
        []),
      startWith([])
    );
}
export class AppComponent {
  coffeeReqs$: Observable<CoffeeRequest> = /* */
  coffeeMaking$: /* */
 
  state$: Observable<CoffeeRequest[]> =
    merge(this.coffeeReqs$, this.coffeeMaking$).pipe(
      scan((
        state: { [key: number]: CoffeeRequest },
        val: CoffeeRequest) => ({
          ...state,
          [val.id]: val,
        }),
        {}),
      map(state => Object.keys(state)
        .map(key => state[key])
        .filter(v => !!v)),
      startWith([])
    );
}
      scan((
        state: { [key: number]: CoffeeRequest },
        val: CoffeeRequest) => ({
          ...state,
          [val.id]: val,
        }),
        {}),
      map(state => Object.keys(state)
        .map(key => state[key])
        .filter(v => !!v)),
  coffeeMaking$: Observable<CoffeeRequest> =
    this.coffeeReqs$.pipe(this.assignBarista());
 
  assignBarista(): OperatorFunction<CofReq, CofReq> {
    return (source: Observable<CoffeeRequest>) =>
      source.pipe(
        delay(1000),
        setStatus(CoffeeRequestStatusValue.pickedUp)
      );
  }
  /* makeCoffee, pickupCoffee */
state$: Observable<CofReq[]> =
    merge(
      this.coffeeReqs$, this.coffeeMaking$,
      this.coffeeDone$, this.coffeePickedUp$)
    .pipe(/* scan, map */)
  coffeeMaking$: Observable<CoffeeRequest> =
    this.coffeeReqs$.pipe(
      delay(1000),
      setStatus(CoffeeRequestStatusValue.pickedUp)
    );
  coffeeDone$: Observable<CoffeeRequest> =
    this.coffeeMaking$.pipe(this.makeCoffee());
  coffeePickedUp$: Observable<CoffeeRequest> =
    this.coffeeDone$.pipe(this.pickupCoffee());

What more...

Live coding

Thank you!

 

Any question?

 

Made with Slides.com