D'après le reactive manifesto, les applications réactives sont :
« Responsive » : capable d’offrir une expérience utilisateur optimale
« Resilient » : de mieux tolérer les erreurs et les pannes
« Elactic » : de mieux utiliser la puissance des machines
« Message-driven » : dirigée par les événements
On parle de modèle de développement :
« event driven » : dirigé par les événements
« push based application », les données sont poussées dès qu’elles sont disponibles
« Hollywood » : Don't call us, we'll call you
« réactif », qui réagit aux événements
Idéal pour les applications qui interagissent en temps réel avec les utilisateurs
var numbers = [1, 2, 3, 4, 5];
function double(number) {
return number * 2;
}
var doubledNumbers = numbers.map(double);
//[2, 4, 6, 8, 10]
var numbers = [1, 2, 3, 4, 5];
function greaterThanThree(value) {
return value > 3;
}
var numbersGreaterThanThree =
numbers.filter(greaterThanThree);
//[4, 5]
var numbers = [1, 2, 3, 4, 5];
function sum(total, value) {
return total + value;
}
var total = numbers.reduce(sum, 0);
//15
function(x) { return x + 1; }
(x) => { return x + 1; }
x => x + 1
var numbers = [1, 2, 3, 4, 5];
var doubledNumbers = numbers.map(n => n * 2);
//[2, 4, 6, 8, 10]
var numbers = [1, 2, 3];
//.values() returns an iterator
var numIterator = numbers.values();
numIterator.next() //{value: 1, done: false }
numIterator.next() //{value: 2, done: false }
numIterator.next() //{value: 3, done: false }
numIterator.next() //{done: true}
var mySet = new Set();
mySet.add(1);
mySet.add(2);
mySet.add(3);
//.values() returns an iterator
var numIterator = mySet.values();
numIterator.next() //{value: 1, done: false }
numIterator.next() //{value: 2, done: false }
numIterator.next() //{value: 3, done: false }
numIterator.next() //{done: true}
var myMap = new Map();
myMap.set('a', 1);
myMap.set('b', 2);
myMap.set('c', 3);
//.values() returns an iterator
var valueIterator = myMap.values();
valueIterator.next() //{value: 1, done: false }
valueIterator.next() //{value: 2, done: false }
valueIterator.next() //{value: 3, done: false }
valueIterator.next() //{done: true}
//.keys() returns an iterator
var keyIterator = myMap.keys();
keyIterator.next() //{value: 'a', done: false }
keyIterator.next() //{value: 'b', done: false }
keyIterator.next() //{value: 'c', done: false }
keyIterator.next() //{done: true}
//.entries() returns an iterator
var entryIterator = myMap.entries();
entryIterator.next() //{value: ['a', 1], done: false }
entryIterator.next() //{value: ['b', 2], done: false }
entryIterator.next() //{value: ['c', 3], done: false }
entryIterator.next() //{done: true}
function* lazyNumbers(){
yield 1;
yield 2;
yield 3;
}
//generator functions return an iterator
var valueIterator = lazyNumbers();
valueIterator.next() //{value: 1, done: false }
valueIterator.next() //{value: 2, done: false }
valueIterator.next() //{value: 3, done: false }
valueIterator.next() //{done: true}
Cela fonctionne uniquement avec des objets synchrones :
Si on demande une valeur à partir d'une collection (« pull »), elle doit être disponible tout de suite
var myButton = document.getElementById('myButton');
function doSomethingOnClick(event){
doSomething(event);
//make sure you remember to tidy up after yourself!
myButton.removeEventListener('click', doSomethingOnClick);
}
myButton.addEventListener('click', doSomethingOnClick);
getStuff(function(result){
getMoreStuff(function(results){
getSomeStuffForEachResult(results, function(moreStuff){
// note my code running off the page...^
doTheThingWeWantedToDo(moreStuff);
});
});
});
function getStuff(url, callback){
var req = new XMLHttpRequest();
function reqListener () {
callback(JSON.parse(req.responseText));
}
req.addEventListener("load", reqListener);
req.open("GET", url);
req.send();
}
getStuff('foos.json', function(foos){
getStuff('bars.json', function(bars){
//do stuff with foos and bars
});
});
fetch('foos.json')
.then(function(res){
return res.json();
})
.then(function(foos){
return fetch('bars.json')
.then(function(res){
return res.json();
})
.then(function(bars){
return [foos, bars];
})
})
.then(function(results){
//do stuff with foos and bars
});
An API for asynchronous programming
with observable streams
The Observer pattern done right
ReactiveX is a combination of the best ideas from
the Observer pattern, the Iterator pattern, and functional programming
Available in Javascript with RxJS
//Observable constructor
const myObservable = new Observable(observer => {
//the observer lets us *push* values
observer.next(1);
observer.next(2);
observer.next(3);
//it lets us propagate errors
observer.error('oops');
//and lets us (optionally) complete the stream
observer.complete();
});
//Observable constructor
const myObservable = new Observable(observer => {
observer.next(1);
observer.next(2);
observer.next(3);
observer.complete();
});
myObservable.subscribe(
val => console.log(val),
err => console.log(err),
_ => console.log('done')
);
//Observable constructor
const myObservable = new Observable(observer => {
let count = 0;
const interval = setInterval(() => {
observer.next(count++);
}, 100);
//disposal function
return () => {
clearInterval(interval);
}
});
const subscriber = myObservable.subscribe(
val => console.log(val),
err => console.log(err),
_ => console.log('done')
);
subscriber.unsubscribe();
(Marble diagram)
--a---b-c---d---X---|->
a, b, c, d are emitted values
X is an error
| is the 'completed' signal
---> is the timeline
(Marble diagram)
function* values(){
yield 1;
yield 2;
yield 3;
}
const i = values();
i.next() //{value: 1, done: false}
i.next() //{value: 2, done: false}
i.next() //{value: 3, done: false}
i.next() //{done: true}
const o = new Observable(observer => {
observer.next(1);
observer.next(2);
observer.next(3);
observer.complete();
});
o.subscribe(
value => console.log(value),
err => console.error(err),
_ => console.log('done')
);
Brian Holt (NetFlix)
Single return value | Multiple return values | |
Pull/Synchronous/Interactive | object | Iterables |
Push/Asynchronous/Reactive | Promise | Observables |
const myButton = document.getElementById('myButton');
const clicks$ = new Observable(observer => {
const onClick = ev => observer.next(ev);
myButton.addEventListener('click', onClick);
return () => {
myButton.removeEventListener('click', onClick);
}
});
const clickListener = clicks$.subscribe(event => console.log(event));
//later...
clickListener.unsubscribe();
const myButton = document.getElementById('myButton');
const clicks$ = Observable.fromEvent(myButton, 'click');
const clickListener = clicks$.subscribe(event => console.log(event));
//ClickEvent
//ClickEvent
//ClickEvent
//later...
clickListener.unsubscribe();
const myInput = document.getElementById('myInput');
//stream of keyup Events
const keyups$ = Observable.fromEvent(myInput, 'keyup');
//map to the values
const inputs$ = keyups$.map(ev => ev.target.value);
inputs$.subscribe(text => console.log(text));
//h
//he
//hel
//hell
//hello
---1----8--3----2------6-->
vvvvv map(v => v + 1) vvvv
---2----9--4----3------7-->
const incrementButton = document.getElementById('increment');
const decrementButton = document.getElementById('decrement');
const counterOutput = document.getElementById('output');
const getValue = ev => parseInt(ev.target.value,10);
const increments$ = Observable.fromEvent(incrementButton, 'click');
const decrements$ = Observable.fromEvent(decrementButton, 'click');
//merge into a single stream
const changes$ = Observable.merge(increments$, decrements$);
//map to int values
const values$ = changes$.map(getValue);
//scan (reduce) to track the state
const total$ = values$.scan((total, value) => total + value, 0);
//set count
total$.subscribe(count => {
counterOutput.innerText = count;
console.log(count);
});
incrementStream(input): --(in)---(in)----------(in)--->
decrementStream(input): ---------------(de)----------->
changesStream(merge): --(in)---(in)--(de)----(in)--->
valuesStream (map): --(+1)---(+1)--(-1)----(+1)--->
totalStream (scan): --(+1)---(+2)--(+1)----(+2)--->
import { Http } from '@angular/http'
const myInput = document.getElementById('myInput');
//stream of keyups Events
const keyups$ = Observable.fromEvent(myInput, 'keyup');
//map to the values
const inputs$ = keyups$.map(ev => ev.target.value);
//flatMap our inputs to *http responses*
const responses$ = inputs$
.flatMap(text => Http.get(`foo.com/search/${text}`))
.map(res => res.json());
responses$.subscribe(text => console.log(text));
//[{name: 'harry'},{name: 'hettie'}, {name: 'hellboy'}];
//[{name: 'harry'}, {name: 'hellboy'}];
//[{name: 'hellboy'}];
requestStream: --a-----b--c--------------|->
responseStream: -----A--------B-----C-----|->
(la requête en minuscule, la réponse en majuscules)
import { Http } from '@angular/http'
const myInput = document.getElementById('myInput');
//stream of click Events
const clicks$ = Observable.fromEvent(myInput, 'keyup');
//map to the values
const inputs$ = clicks$.map(ev => ev.target.value);
//flatMap our inputs to *http responses*
const responses$ = inputs$
.switchMap(text => Http.get(`foo.com/search/${text}`))
.map(res => res.json());
responses$.subscribe(text => console.log(text));
//[{name: 'harry'},{name: 'hettie'}, {name: 'hellboy'}];
//[{name: 'harry'}, {name: 'hellboy'}];
//[{name: 'hellboy'}];
const responses$ =
http.get('somebadconnection.json')
.retry(3)
.map(res => res.json());
responses$.subscribe(
res => console.log(res),
err => console.log('could not connect!')
);
const ticks$ = Observable.interval(5000);
const responses$ =
ticks$
.switchMap(() => http.get('stocks.json'))
.map(res => res.json());
const stockPoller = responses$.subscribe(res => console.log(res));
//later
stockPoller.unsubscribe();
class MyAutoCompleteComponent {
constructor(http:Http, formBuilder:FormBuilder){
this.searchForm = formBuilder.group({
name: ['', Validators.required]
});
this.people = searchForm.controls.name.valueChanges
.switchMap(text => Http.get(`foo.com/search/${text}`))
.map(res => res.json());
}
}
<form novalidate [formGroup]="searchForm">
<input type="text" formControlName="name">
</form>
<div>
<ul>
<li *ngFor="let person of people | async">{{person.name}}</li>
</ul>
</div>
import { Http } from '@angular/http';
import { ActivatedRoute } from '@angular/router';
export class MyComponent {
constructor(
private http: Http,
private route: ActivatedRoute) {}
ngOnInit() {
this.data = this.route.params
.map(params => params['id'])
.switchMap(id => this.http.get('${id}/data.json'));
}
}
import { Store } from '@ngrx/store';
import { INCREMENT, DECREMENT, RESET } from './counter';
interface AppState {
counter: number;
}
@Component({
selector: 'my-app',
template: `
<button (click)="increment()">Increment</button>
<div>Current Count: {{ counter | async }}</div>
<button (click)="decrement()">Decrement</button>
<button (click)="reset()">Reset Counter</button>
`
})
class MyAppComponent {
counter: Observable<number>;
constructor(private store: Store<AppState>){
this.counter = store.select('counter');
}
increment(){
this.store.dispatch({ type: INCREMENT });
}
decrement(){
this.store.dispatch({ type: DECREMENT });
}
reset(){
this.store.dispatch({ type: RESET });
}
}