Observable
Reactive Programming
Тимур Хазамов
Инфраструктура фронтэнда
Maintainer of React UI
ATTENTION
Slides are full of JavaScript
Please be patient and keep calm
!
Typical callbacks
Array callbacks
const floors =
[9, 10, 8, 7, 6, 5, 4, 3, 2, 1]
function log(...args) {
console.log(args)
}
floors.forEach(log)
DOM callbacks
const clickerEl = document
.getElementById('clicker')
function log(...args) {
console.log(args)
}
clickerEl
.addEventListener('click', log)
NodeJS callbacks
const fs = require('fs')
const fname = '~/doklad.txt'
function log(err, data) {
if (err) throw err
console.log(data)
}
fs.readFile(fname, log)
Promise callbacks
const url =
'www.vsedokladi.ru/konfur2016'
function log(data) {
console.log(data)
}
function logErr(err) {
console.error(err)
}
fetch(url)
.then(x => x.json())
.then(log, logErr)
Stream callbacks
const stream = fs.createReadStream('~/doklad.txt')
function log(data) {
console.log(data)
}
function logErr(err) {
console.error(err)
}
function logDone() {
console.log('Hurray!')
}
stream.on('data', log)
stream.on('error', logErr)
stream.on('done', logDone)
and more
3 (three) callbacks
- onNext(data)
- onError(error)
- onComplete()
Super Cool Handler
function superCoolDataSource(
onNext,
onError,
onDone
) {
/* event handling */
}
Super Cool Handler
function superCoolDataSource(
onNext,
onError,
onDone
) {
[1, 2, 3].forEach(onNext)
onDone()
}
Super Cool Handler
function superCoolDataSource(
onNext,
onError,
onDone
) {
fetch(url)
.then(onNext, onError)
.then(onDone)
}
Not cool enough
Observer
const logObserver = {
next(data) { log(data) },
error(err) { logErr(err) },
complete() { logDone() }
}
Observer
const logObserver = {
next(data) { log(data) },
error(err) { logErr(err) },
complete() { logDone() }
}
function supaCoolDataSource(obs) {
fetch(url)
.then(obs.next, obs.error)
.then(obs.done)
}
Observer
const logObserver = {
next(data) { log(data) },
error(err) { logErr(err) },
complete() { logDone() }
}
function subscribe(obs) {
fetch(url)
.then(obs.next, obs.error)
.then(obs.done)
}
Observable
const observable = {
subscribe(obs) {
fetch(url)
.then(obs.next, obs.error)
.then(obs.done)
}
}
observable.subscribe(logObserver)
Observable
const observable = {
subscribe(obs) {
[9, 10, 8].forEach(obs.next)
obs.done()
}
}
observable.subscribe(logObserver)
Observable
const observable = {
subscribe(obs) {
fs.readFile(fname, (err, data) => {
if (err) obs.error(err)
else obs.next(data)
obs.done()
})
}
}
observable.subscribe(logObserver)
Observable Constructor
function createObservable(subscribe) {
return {
subscribe: subscribe
}
}
const arrayObservable =
createObservable(obs => {
[9, 10, 8].forEach(obs.next)
obs.done()
})
Be fluent
Observable Extensions
type ObservableExtension =
(this: Observable, ...args: any[])
=> Observable
function map(mapFn) {
return createObservable(obs => {
this.subscribe({
next(x) { obs.next(mapFn(x)) },
error(e) { obs.error(e) },
complete() { obs.complete() }
})
})
}
Observable Extensions
function createObservable(subscribe) {
return {
subscribe: subscribe,
map: map
}
}
const newObservable = arrayObservable
.map(x => x * 2)
Observable Extensions
function createObservable(subscribe) {
return {
subscribe,
map,
filter,
scan,
delay,
merge,
throttle
}
}
EcmaScript Proposal
Stage 1 Draft August 12 2016
https://tc39.github.io/proposal-observable/
~ 120 Extensions
fromEvent

// Emits clicks happening on the DOM document
var clicks = Rx.Observable
.fromEvent(document, 'click')
clicks
.subscribe(x => console.log(x))
merge

filter

debounce

Demo
const bmi$ = Rx.Observable
.combineLatest(
weight$, height$,
(w, h) => w / (h * h)
)
bmi$
.subscribe(bmi => {
const {text, status} =
getBMIDescription(bmi)
bmiResult.innerText = text
bmiResult.className = status
})
const user$ = request$
.withLatestFrom(input$, (a, b) => b)
.filter(Boolean)
.switchMap((username) =>
Rx.Observable.fromPromise(
api.getUser(username)
))
.takeUntil(cancel$)
user$
.subscribe({
next(user) {
cancelButton.style.display = 'none'
userEl.innerHTML = renderUser(user)
},
error(error) {
userEl.innerHTML = renderError(error)
cancelButton.style.display = 'none'
user$.subscribe(this)
}
})
Go on... Show the autocomplete
RxJS autocomplete
componentDidMount() {
this.subscription = this.keyup$
.debounceTime(500)
.switchMap(e =>
http.get(`/autocomplete?q=${e.target.value}`)
)
.subscribe(values => this.setState({ values }))
}
Pure JS autocomplete
handleKeyUp(e) {
e.preventDefault()
if (this.timeoutId) {
clearTimeout(this.timeoutId)
}
this.timeoutId = setTimeout(() => {
if (this.xhr) {
this.xhr.abort()
}
const xhr = this.xhr = new XMLHttpRequest()
xhr.responseType = 'json'
xhr.onload = e => {
const values = xhr.response
this.setState({ values })
this.xhr = null;
}
xhr.open('GET', `/autocomplete?q=${e.target.value}`)
xhr.send()
}, 500)
}
Pros
Observable
- Any number of values
- Over any amount of time
- Lazy
- Cancellable
- Can be retried/replayed
Some Fun (:
Thanks!

Observable
By t1mmaas
Observable
- 851