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