à la découverte des observables - humantalks paris 2017
TRANSCRIPT
@nicoespeon
À la découverte des Observables
Meetup HumanTalks Paris Décembre 2017
1
@nicoespeon 2
@nicoespeon 3
@nicoespeon 4
Back-end
1
23
@nicoespeon 5
$search.on('click', () !=> { fetchTrips() .then(({data}) !=> renderFoundTrips(data))
.catch(handleError) })
@nicoespeon 6
@nicoespeon 7
Back-end
1
3
2
4
Si c’est incomplet, on « poll »
@nicoespeon 8
$search.on('click', () !=> { fetchTrips() .then(renderFoundTripsAndPoll) .catch(handleError) })
function renderFoundTripsAndPoll({data, isComplete}) { renderFoundTrips(data) if (!isComplete) { pollTrips() .then(renderFoundTripsAndPoll) .catch(handleError) } }
@nicoespeon 9
@nicoespeon 10
Back-end
1
3
2
4
On optimise le polling
5s
@nicoespeon 11
$search.on('click', () !=> { fetchTrips() .then(renderFoundTripsAndPoll) .catch(handleError) })
function renderFoundTripsAndPoll({data, isComplete}) { const DELAY_BEFORE_POLLING_IN_MS = 5000
renderFoundTrips(data) if (!isComplete) { setTimeout(() !=> { pollTrips() .then(renderFoundTripsAndPoll) .catch(handleError) }, DELAY_BEFORE_POLLING_IN_MS ) } }
@nicoespeon
« Et si on lançait la recherche au fur et à
mesure de la saisie ? »
12
@nicoespeon 13
const DEBOUNCE_TIME_IN_MS = 250 let fetchTripsTimeout
$input.on('keypress', () !=> { clear(fetchTripsTimeout) fetchTripsTimeout = setTimeout(() !=> { fetchTrips() .then(renderFoundTripsAndPoll) .catch(handleError) }, DEBOUNCE_TIME_IN_MS); })
function renderFoundTripsAndPoll({data, isComplete}) { const DELAY_BEFORE_POLLING_IN_MS = 5000 renderFoundTrips(data) if (!isComplete) { setTimeout(() !=> { pollTrips() .then(renderFoundTripsAndPoll) .catch(handleError) }, DELAY_BEFORE_POLLING_IN_MS ) } }
@nicoespeon 14
const DEBOUNCE_TIME_IN_MS = 250 let fetchTripsTimeout
$input.on('keypress', () !=> { clear(fetchTripsTimeout) fetchTripsTimeout = setTimeout(() !=> { fetchTrips() .then(renderFoundTripsAndPoll) .catch(handleError) }, DEBOUNCE_TIME_IN_MS); })
function renderFoundTripsAndPoll({data, isComplete}) { const DELAY_BEFORE_POLLING_IN_MS = 5000 renderFoundTrips(data) if (!isComplete) { setTimeout(() !=> { pollTrips() .then(renderFoundTripsAndPoll) .catch(handleError) }, DELAY_BEFORE_POLLING_IN_MS ) } }
@nicoespeon 15
Back-end
1
2
3
Nous avons un bug…
4
Oups
@nicoespeon 16
@nicoespeon
Nicolas Carlo
17
@nicoespeon
Servez-vous des Observables pour gérer des scénarios asynchrones compliqués
18
@nicoespeon
Si on modélisait ça autrement ?
19
Un événement
Une erreur
Flux terminé
temps
= Observable
@nicoespeon
- Andre Staltz, 2 minute introduction to Rx
« Think of it as an asynchronous
immutable array »
20
@nicoespeon
Utilisez les Observables avec ReactiveX
• Projet porté par Microsoft, en open-source depuis 2012
• Implémente les Observables dans de nombreux langages (Java, Python, JS, .NET, Go, Ruby, Elixir, Swift…)
• Fournit une API pour manipuler des flux observables
21
@nicoespeon 23
@nicoespeon
clickStream ➜ click$
24
@nicoespeon
Fetch au click
25
flatMap
click$
fetchedTrips$
@nicoespeon 26
const fetchedTrips$ = fromEvent($search, 'click') .flatMap(fetchTrips)
fetchedTrips$.subscribe( ({data}) !=> renderFoundTrips(data), handleError )
@nicoespeon
Le polling
27
flatMap
isRequestComplete$
polledTrips$
filter
@nicoespeon 28
const isRequestComplete$ = new Subject()
const fetchedTrips$ = fromEvent($search, 'click') .flatMap(fetchTrips)
const polledTrips$ = isRequestComplete$ .filter((isComplete) !=> !isComplete) .flatMap(pollTrips)
const foundTrips$ = merge(fetchedTrips$, polledTrips$)
foundTrips$.subscribe( ({data, isComplete}) !=> { renderFoundTrips(data) isRequestComplete$.next(isComplete) }, handleError )
@nicoespeon
Le polling optimisé
29
flatMap
isRequestComplete$
polledTrips$
delay
@nicoespeon 30
const isRequestComplete$ = new Subject()
const fetchedTrips$ = fromEvent($search, 'click') .flatMap(fetchTrips)
const DELAY_BEFORE_POLLING_IN_MS = 5000 const polledTrips$ = isRequestComplete$ .filter((isComplete) !=> !isComplete) .delay(DELAY_BEFORE_POLLING_IN_MS ) .flatMap(pollTrips)
const foundTrips$ = merge(fetchedTrips$, polledTrips$)
foundTrips$.subscribe( ({data, isComplete}) !=> { renderFoundTrips(data) isRequestComplete$.next(isComplete) }, handleError )
@nicoespeon
« Et si on lançait la recherche au fur et à
mesure de la saisie ? »
31
@nicoespeon
Fetch au fur et à mesure
32
flatMap
keypress$
fetchedTrips$
debounce
@nicoespeon 33
const isRequestComplete$ = new Subject()
const DEBOUNCE_TIME_IN_MS = 250 const fetchedTrips$ = fromEvent($input, 'keypress') .debounce(DEBOUNCE_TIME_IN_MS) .flatMap(fetchTrips)
const DELAY_BEFORE_POLLING_IN_MS = 5000 const polledTrips$ = isRequestComplete$ .filter((isComplete) !=> !isComplete) .delay(DELAY_BEFORE_POLLING_IN_MS ) .flatMap(pollTrips)
const foundTrips$ = merge(fetchedTrips$, polledTrips$)
foundTrips$.subscribe( ({data, isComplete}) !=> { renderFoundTrips(data) isRequestComplete$.next(isComplete) }, handleError )
@nicoespeon 34
@nicoespeon
FlatMapLatest
35
reactivex.io/documentation/operators/flatmap.html
@nicoespeon 36
const isRequestComplete$ = new Subject()
const DEBOUNCE_TIME_IN_MS = 250 const fetchedTrips$ = fromEvent($input, 'keypress') .debounce(DEBOUNCE_TIME_IN_MS) .flatMapLatest(fetchTrips)
const DELAY_BEFORE_POLLING_IN_MS = 5000 const polledTrips$ = isRequestComplete$ .filter((isComplete) !=> !isComplete) .delay(DELAY_BEFORE_POLLING_IN_MS ) .flatMapLatest(pollTrips)
const foundTrips$ = merge(fetchedTrips$, polledTrips$)
foundTrips$.subscribe( ({data, isComplete}) !=> { renderFoundTrips(data) isRequestComplete$.next(isComplete) }, handleError )
@nicoespeon 37
const isRequestComplete$ = new Subject()
const DEBOUNCE_TIME_IN_MS = 250 const fetchedTrips$ = fromEvent($input, 'keypress') .debounce(DEBOUNCE_TIME_IN_MS) .flatMapLatest(fetchTrips)
const DELAY_BEFORE_POLLING_IN_MS = 5000 const polledTrips$ = isRequestComplete$ .filter((isComplete) !=> !isComplete) .delay(DELAY_BEFORE_POLLING_IN_MS ) .flatMapLatest(pollTrips)
const foundTrips$ = merge(fetchedTrips$, polledTrips$)
foundTrips$.subscribe( ({data, isComplete}) !=> { renderFoundTrips(data) isRequestComplete$.next(isComplete) }, handleError )
@nicoespeon
Qu’avons-nous gagné ?
✓ Code déclaratif, facile à faire évoluer
✓ Logique applicative sans effets de bords, facile à tester
✓ Observables composables, facile à ré-utiliser
38
@nicoespeon
L’usage des Observables se popularise dans le web
• Embarqué par défaut dans Angular
• Il y a des adaptations pour chaque librairie : redux−observable, vue-rx…
• Cycle.js est un framework fonctionnel et réactif, basé sur l’usage des Observables
39
@nicoespeon
Pensez aux Observables pour résoudre vos
problèmes asynchrones non triviaux
40
@nicoespeon 41
Bienvenue sur la route de la programmation réactive !
@nicoespeon
Pour aller plus loinThe introduction to Reactive Programming you've been missing
RxJS Functions with Examples
Using Observables in real life
Testing reactive code
📚 Reactive Programming - What is RxJS? (lessons)
🎦 The whole future declared in a var (video)
📑 Dynamics of Change - Why Reactivity matters (PDF)
🥋 Reactive Extensions (Rx.js) Workshop (kata)
42
@nicoespeon
Des diagrammes Marbles dans le code
43
const polledTrips$ = isRequestComplete$ !// !!---(true)----(false)----------(false)-------> .filter((isComplete) !=> !isComplete) !// ------------(false)----------(false)--------> .delay(DELAY_BEFORE_POLLING_IN_MS ) !// ----------------(false)----------(false)----> .flatMapLatest(pollTrips) !// ----------------(A)--------------(A)-------->
@nicoespeon
Analysons les strates du code
44
Gestion du temps (debounce, délai)
Logique applicative
Effets de bord (rendu, gestion des erreurs)
Évènements du DOM (click, keypress)
@nicoespeon 45
const DEBOUNCE_TIME_IN_MS = 250 let fetchTripsTimeout
$input.on('keypress', () !=> { clear(fetchTripsTimeout) fetchTripsTimeout = setTimeout(() !=> { fetchTrips() .then(renderFoundTripsAndPoll) .catch(handleError) }, DEBOUNCE_TIME_IN_MS); })
function renderFoundTripsAndPoll({data, isComplete}) { const DELAY_BEFORE_POLLING_IN_MS = 5000 renderFoundTrips(data) if (!isComplete) { setTimeout(() !=> { pollTrips() .then(renderFoundTripsAndPoll) .catch(handleError) }, DELAY_BEFORE_POLLING_IN_MS ) } }
@nicoespeon 46
const isRequestComplete$ = new Subject()
const DEBOUNCE_TIME_IN_MS = 250 const fetchedTrips$ = fromEvent($input, 'keypress') .debounce(DEBOUNCE_TIME_IN_MS) .flatMapLatest(fetchTrips)
const DELAY_BEFORE_POLLING_IN_MS = 5000 const polledTrips$ = isRequestComplete$ .filter((isComplete) !=> !isComplete) .delay(DELAY_BEFORE_POLLING_IN_MS ) .flatMapLatest(pollTrips)
const foundTrips$ = merge(fetchedTrips$, polledTrips$)
foundTrips$.subscribe( ({data, isComplete}) !=> { renderFoundTrips(data) isRequestComplete$.next(isComplete) }, handleError )
@nicoespeon 47
14 alternances
Effets de bords couplés à la logique applicative
6 alternances
Effets de bords isolés