Skip to content

Latest commit

 

History

History
392 lines (289 loc) · 8.25 KB

slides.md

File metadata and controls

392 lines (289 loc) · 8.25 KB
highlighter lineNumbers layout title titleTemplate favicon transition css
shiki
true
cover
RxJS fundamentals
%s
slide-left
unocss
RxJS logo

RxJS fundamentals


layout: quote

ReactiveX is a combination of the best ideas from the Observer pattern, the Iterator pattern, and functional programming.

Promises vs Observables

Promises…

  • only resolve once
  • cannot be canceled
  • are always asynchronous
  • execute on creation (eager).

Observables…

  • handle any number of events
  • can be canceled
  • can be synchronous or asynchronous
  • execute when subscribed (lazy).

Observable comparisons

Promise
  .resolve(1)
  .then((value) => {
    console.log(value)
  })
import { of, tap } from 'rxjs'

of(1)
  .pipe(
    tap((value) => {
      console.log(value)
    })
  )
  .subscribe()
[1, 2, 3]
  .map((n) => n * 2)
  .filter((n) => n < 5)
  .reduce((acc, n) => acc + n, 0)

import { from, map, filter, reduce } from 'rxjs'

from([1, 2, 3])
  .pipe(
    map((n) => n * 2),
    filter((n) => n < 5),
    reduce((acc, n) => acc + n, 0)
  )
  .subscribe()

Observable anatomy

import { of, map, filter } from 'rxjs'

const subscription = of()   // a "creation" function
  .pipe(
    map(),                  // operators
    filter(),
    // …
  )
  .subscribe()              // nothing happens until this is called

// later:
subscription.unsubscribe()  // clean up to prevent memory leaks

The observer

import { of } from 'rxjs'

of(1, 2, 3).subscribe({     // subscribe() accepts an observer
  next: (n) => {            // next() is called for each value
    console.log('Next:', n)
  },
  complete: () => {         // complete() is called without arguments
    console.log('Done!')
  },
  error: (err) => {         // in case of an error
    console.error(err)
  }
})

// Next: 1
// Next: 2
// Next: 3
// Done!

Combining observables

import { fromEvent, interval, merge, combineLatest, zip, withLatestFrom } from 'rxjs'

const clicks$ = fromEvent(document, 'click')
const seconds$ = interval(1000)

merge(clicks$, seconds$).subscribe((clickOrSecond) => {
  console.log(clickOrSecond) // either a click or an interval
})

combineLatest([clicks$, seconds$]).subscribe(([latestClick, latestSecond]) => {
  console.log(latestClick, latestSecond) // latest click and second when either emits
})

zip(clicks$, seconds$).subscribe(([latestClick, latestSecond]) => {
  console.log(latestClick, latestSecond) // latest click and second when both have emitted
})

clicks$
  .pipe(withLatestFrom(seconds$))
  .subscribe(([latestClick, latestSecond]) => {
    console.log(latestClick, latestSecond) // latest click and latest second for each click
  })

Game architecture (first attempt)

flowchart LR
  ticks["Game loop"] --> next
  subgraph Entities
    clicks$ --> ball$
    mousemoves$ --> paddle$
    ball$ & paddle$ & bricks$ & lives$ & score$
  end
  subgraph Renderers
    direction LR
    renderBall & renderPaddle & renderBricks & renderLives & renderScore
  end
  Entities --> next["nextState()"] --> render["renderState()"]
  render --> Renderers
Loading

Subjects

Consider the relation between observable and observer:
import { Observable } from 'rxjs'

// same as: `of('Hello')`
const observable$ = new Observable((observer) => {
  observer.next('Hello')
  observer.complete()
})
const observer = {
  next:     (value) => console.log(value),
  complete: () =>      console.log('Done!'),
}

observable$.subscribe(observer)


// Hello

// Done!
A Subject is an observable and an observer:
import { Subject } from 'rxjs'


const subject = new Subject()



const observer = {
  next:     (value) => console.log(value),
  complete: () =>      console.log('Done!'),
}

subject.subscribe(observer)

subject.next('Hello')
// Hello
subject.complete()
// Done!

Subject as an observer

import { Subject, map, of } from 'rxjs'

const subject = new Subject()
const transform = map(value => value * 2)
const observer = (value) => console.log(value)

subject
  .pipe(transform)
  .subscribe(observer)

const numbers$ = of(1, 2, 3)

numbers$.subscribe(subject)
// 2
// 4
// 6

subject.next(4)
// 8
flowchart TB
  numbers$ --> subject
  subject -- transform --> observer
  subject --> subject
Loading

Behavior subjects

A Subject with a "current value". Ideal for reading/writing state.
import { BehaviorSubject } from 'rxjs'

const state = new BehaviorSubject(0)

state.subscribe(value => console.log(`A: ${value}`))
// A: 0
state.next(1)
// A: 1

state.subscribe(value => console.log(`B: ${value}`))
// B: 1
state.next(2)
// A: 2
// B: 2

state.value
// 2

Game architecture (with BehaviorSubjects)

flowchart LR
  ticks["Game loop"] --> next
  subgraph Entities
    clicks$ --> ball$
    mousemoves$ --> paddle$
    ball$ & paddle$ & bricks$ & lives$ & score$
  end
  subgraph Renderers
    direction LR
    renderBall & renderPaddle & renderBricks & renderLives & renderScore
  end
  Entities --> next["nextState()"] --> update["updateState()"] --> render["renderState()"]
  update --> Entities
  render --> Renderers
Loading