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?
Observables - hot or not - 20190207 MeetJS Warszawa
By Chris Trześniewski
Observables - hot or not - 20190207 MeetJS Warszawa
- 403