Reactive Extensions in JavaScript

An API for asynchronous programming

Don't we have that already?

 

Promises

Async / await

Box type

const Box = (x) => 
({
  value: x,
  map: (f) => Box(f(x)),
  fold: (f) => f(x)
});

const identity = x => x;
const upper = (x) => x.toUpperCase();
const lower = (x) => x.toLowerCase();
const concat = (str) => (value) => value + str;
const vowels = /a|e|i|o|u/gi;
const replace = (pattern, v) => (s) => s.replace(pattern, v);

Operating on the box type

const myValue = Box("Belfast") 	// Box(Belfast)
  .map(upper)  			// Box(BELFAST)
  .map(concat(" JavaScript"))   // Box(BELFAST JavaScript)
  .map(replace(vowels, ""))	// Box(BLFST Jvscrpt)
  .map(lower)			// Box(blfst jvscrpt)

console.log("box value:", myValue.fold(identity)); //box value: blfst jvscrpt

Imperative approach

Introduces state to keep track of control flow.

const inputWord = "Belfast";
const inputWordUpper = inputWord.toUpperCase();
const inputWordJs = inputWordUpper + " JavaScript";
const inputWordUpperNoVowels = inputWordUpper.replace(vowels, "");
const inputWordLower = inputWordUpperNoVowels.toLowerCase();

console.log("inputWordLower", inputWordLower);

Observable

An interface for dealing with async events.

(Lodash for async)

Observables from events

const node = document.querySelector("#yo");

const source = Rx.Observable
  .fromEvent(node, "click")
  .filter(x => x.clientY < 100)
  .bufferCount(3)
  .subscribe(console.log)

Observables are lazy

const source = Rx.Observable
    .range(0, Infinity)
    .filter(x => x % 2 === 0)
    .take(5)

const subscribe = source.subscribe(val => console.log(val));

//0
//2
//4
//6
//8
fetchAttendees()
    .then((attendees) => 
        attendees.filter((attendee) => attendee.company === "Rapid7"))
    .then((attendees) => 
  	attendess.map((attendee) => attendee.name))
    .then((attendees) => 
  	attendees.forEach(console.log));    
    

The pieces we care about are sitting behind then()

Promises

const attendees = await fetchAttendees()

const rapid7Attendees = attendees
	.filter((attendee) => attendee.company === 'Rapid7'));

const rapid7AttendeeNames = rapid7Attendees.map((attendee) => attendee.name));

rapid7AttendeeNames.forEach(console.log);    
    

We run into the same control flow problem we had before. We now have state.

Async / await

fetchAttendees()
  .mergeAll()
  .filter(propEq("company", "Rapid7"))
  .map(prop("name"))
  .subscribe(console.log);

Observable

const API_BASE_URL = "https://api.meetup.com";
const EVENT_NAME = "Belfast-JS";

const eventUrl = `${BASE_URL}/${EVENT_NAME}/events?key${API_KEY}&sign=true`;
const rsvps = (eventId) => `${BASE_URL}/${EVENT_NAME}/${eventId}/rsvps?key${API_KEY}&sign=true`;

const rsvps = Rx.Observable
  .ajax(eventUrl)
  .switchMap((events) => 
        Rx.Observable.ajax(eventsUrl(events[0]))
            .map((rsvps) => 
        	rsvps.member.photoUrl)
  );
  
const rsvpPhotos = rsvps
    .concatMap(Rx.Observable.ajax);
  
const delayRsvps = rsvps
    .concatMap((url) => 
      Rx.Observable.ajax(url).delay(3000));

Composing streams

const delayRsvps$ = rsvps
    .concatMap((url) => 
      Rx.Observable.ajax(url).delay(3000));

const bufferedClicks$ = Rx.Observable
  .fromEvent(node, "click")
  .filter(x => x.clientY < 100)
  .bufferCount(3)


delayRsvps$
    .takeUntil(bufferedClicks$)
    .subscribe(console.log)

Cancellation api

What can promises do that an observable can't?

Nothing

Thanks

for listening

Reactive Extensions JS

By Daniel Skelton

Reactive Extensions JS

  • 960