Reactive programing with RxJS
slides: bit.ly/bjs-rxjs
Andrei Antal
14.04.2020
Andrei Antal
@andrei_antal
- frontend engineer, since i can remember
- currently doing consulting and training @JSLeague
- web & JS technologies enthusiast
- UX and accessibility passionate
- perpetual learner
Frontend Developer
organizer for ngBucharest
@ngBucharest
groups/angularjs.bucharest
Hello!
JSLeague.ro
Contents
Reactive programming and RxJS
- Reactive programming
- Observables
- Creating observables
- Operators
- Higher order observables
- Error-handling
- Examples
Reactive programming
System complexity
Reactive Programming
Product
Vendor
Cart
Cupon
Sale
Payment
User profile
Invoice
Reactive Programming
Cart
Invoice
Add product...
...update the total
Reactive Programming
Cart
Invoice
import {Invoice} from './invoice';
class Cart {
constructor(invoice: Invoice){ ... }
addProduct(product) {
...
this.invoice.update(product); // needs to be public
...
}
}
Passive
Proactive
Reactive Programming
Cart
Invoice
import {Cart} from './cart';
class Invoice {
constructor(cart: Cart){ ... }
init() {
...
this.cart.onCartChange( callback )
}
}
Reactive
Listenable
Reactive Programming
Cart
Cupon
Sale
Payment
Invoice
- What does it affect? - look inside
- How does it work? - find usages
Passive programming
- remote setters and updates
Reactive Programming
Cart
Cupon
Sale
Payment
Invoice
- What does it affect? - find usages of events
- How does it work? - look inside
Reactive programming
- events, observation and self updates
Reactive Programming
// concert spectators
let inMainStand = 300;
let inVipStand = 50;
// code...
let total = inMainStand + inVipStand; // 350
// code...
inMainStand += 60;
console.log(total); // total = ?
total = inMainStand + inVipStand; // 410
- imperatively recalculate the value of total
Reactive Programming
Reactive Programming
Interruptions in web applications
- API events
- WebSockets
- User-triggered events
- Timers (setTimeout, setInterval)
Managing side-effects
SYNC
ASYNC
SINGLE
MULTIPLE
function
Reactive Programming
SYNC
Reactive Programming
function add(a, b) {
return a + b;
}
add(1,2); // imediately returns 3
add(2,3); // imediately returns 4
Reactive Programming
Managing side-effects
SYNC
ASYNC
SINGLE
MULTIPLE
function
generators
Reactive Programming
function *generatorFunction() {
yield 'Hello, ';
yield 'World!';
}
const generatorObject = generatorFunction();
generatorObject.next(); // {value: "Hello, ", done: false}
generatorObject.next(); // {value: "World!", done: false}
generatorObject.next(); // {value: undefined, done: true}
Reactive Programming
Managing side-effects
SYNC
ASYNC
SINGLE
MULTIPLE
function
promises
generators
Reactive Programming
const promise = new Promise((resolve, reject) => {
someAsyncFunction(err, data) {
if(err) return reject(err);
resolve(data)
}
});
promise
.then(value => console.log(value))
.catch(error => console.log(err))
Reactive Programming
Reactive Programming
Managing side-effects
SYNC
ASYNC
SINGLE
MULTIPLE
function
promises
generators
observables
PULL
PUSH
Observables
OBSERVABLES
- A stream of data ( 0 or more values of any type)
- Pushed over any amount of time (can end, but not necessarily)
- Cancelable ( can be stopped from emitting a value - unsubscribed)
- Lazy - won't emit values until we subscribe to them
Reactive Programming
Observer pattern
Iterator pattern
getData(
function(successResult) {
// do something with the data
},
function(faliureError) {
// handle the error
},
);
Async with callbacks
AJAX with promises
result
.then(...)
.then(...)
.then(...)
let result = fetch('api/users.json');
result
.then(success => {
// handle success
})
.catch(error => {
// handle error
})
Async/await
function asyncTask(i) {
return new Promise(resolve => resolve(i + 1));
}
async function runAsyncTasks() {
const res1 = await asyncTask(0);
const res2 = await asyncTask(res1);
const res3 = await asyncTask(res2);
return "Everything done"
}
runAsyncTasks().then(result => console.log(result));
Observables
Unifying callbacks, promises and event handlers
Shape of an observable:
- A function
- accepts an observer ( an object with `next`, `error` and `complete` methods on it)
- returns a cancellation function
Excersises: bit.ly/bjs-rxjs-ex1
Creating an Observable
let observable$ = new Observable(() => {
})
Observable
- read-only - consumer
- plain function
- exposes 3 channels: next, error and complete
Creating an Observable
let observable$ = new Observable(observer => {
observer.next(1);
observer.next(2);
})
Observer
- write-only - producer
- instance passed to observable
- provide next, error and complete methods
- just an interface
Creating an Observable
let observable$ = new Observable(observer => {
observer.next(1);
observer.next(2);
})
observable$.subscribe(value => {
console.log(value)
}
Subscription
- triggers the observable execution
- returns an unsubscribe() method that stops the observable
Creating an Observable
let observable$ = new Observable(observer => {
observer.next(1);
observer.next(2);
return () => {
// cleanup resources when done
};
})
const subscription = observable$.subscribe(value => {
console.log(value)
})
subscription.unsubscribe(); // stop emitting values
Creating an Observable
let observable$ = new Observable(observer => {
observer.next(1);
observer.next(2);
observer.error(new Error('Bad'));
})
const subscription = observable$.subscribe(
value => { console.log(value)},
error => { console.log(error.message)}
)
Creating an Observable
Creation functions
-
of(value1, value2, value3)
-
from(promise/itterable/observable)
-
fromEvent(target, eventName)
-
interval(time)
-
timer(time)
Hot vs Cold observables
COLD is when your observable creates the producer
// COLD
var cold = new Observable((observer) => {
var producer = new Producer();
// have observer listen to producer here
});
- producer is created and activated during subscription
- unicast => everyone gets their own instance
- observables are "cold" by default
Hot vs Cold observables
-
HOT is when your observable closes over the produce
// HOT
var producer = new Producer();
var cold = new Observable((observer) => {
// have observer listen to producer here
});
- producer is created and activated outside and independent of subscription
- multicast => shared reference to the producer
Subjects
Observables are unicast - each subscriber manages its own execution context
Subjects are multicast observables - values are multicasted to many Observers
Types of subjects
- BehaviorSubject - has the notion of "current value"
- ReplaySubject - can record part of it's execution
Operators
Observables are collections of pushed values or events that we can:
- query (filter)
- transform (map)
- accumulate (reduce)
- join
- flatten
- more...
Array functions
let array = [1,2,3,4,5];
array
.map(v => v * v)
.filter(v => v > 5 && v < 20)
.reduce((acc, curr) => acc + curr, 0)
// 25
Array functions
Operators
let observable$ = from([1,2,3,4,5]);
observable$
.pipe(
map(v => v * v),
filter(v => v > 5 && v < 20),
reduce((acc, curr) => acc + curr, 0)
)
.subscribe(val => console.log(val))
// 25
Higher order observbles
const buttonObs$ = fromEvent(querySelector('button'), 'click');
buttonObs$.subscribe(() => {
http$.get('/api/users').subscribe( data => {
// handle loaded data
})
})
Error Handling
const obs$ = new Observable(observer => {
observer.closed; // false
observer.next(1); // emit 1
observer.complete();
observer.closed; // true
observer.next(2); // won't emit
});
- Completing a stream
Error Handling
- A stream can only error once
const obs$ = new Observable(observer => {
observer.closed; // false
observer.next(1); // emit 1
observer.error(new Error('Bad!'));
observer.closed; // true
observer.next(2); // won't emit
});
obs$
.subscribe({
next: v => console.log(v),
error: e => console.log(e.message)
});
Error Handling
- A stream can only error once
const obs$ = new Observable(observer => {
observer.next(1);
observer.error(new Error('BANG!'));
}).pipe(
catchError(err => {
console.log('Intercepted error:' + err);
return of('I got this');
})
)
obs$.subscribe(v => console.log(v));
// 1 'I got this'
Error Handling
- Recovering after an error - the retry operator
const getData$ = http.get('/api/users')
.pipe(
retry(3),
catchError(() => of('Something went wrong');
)
getData$.subscribe(value => console.log(value));
Error Handling
- Recovering after an error - the retry operator
const getData$ = http.get('/api/users')
.pipe(
retry(3),
catchError(() => of('Something went wrong');
)
getData$.subscribe(value => console.log(value));
Thank You!
BucharestJS April 2020 Meetup - RxJS
By Andrei Antal
BucharestJS April 2020 Meetup - RxJS
BucharestJS April 2020 Meetup
- 996