In short, the idea of rx.js is to let you handle complex events like arrays.

This article explains the idea behind rx.js step by step. (Does not teach API usage)


All of our web applications are asynchronous:

  1. The script to load
  2. player
  3. The data access
  4. animation
  5. DOM event binding, data event binding

Asynchronous programming

function play(movieId, cancelButton, callback){
    let movieTicket
    let playError
    let tryFinish = (a)= >{
        if(playError){
            callback(null, playError)
        }else if(movieTicket && player.initialized){
            callback(null, movieTicket)
        }
    }
    cancelButton.addEventListener('click', ()=>{ playError = 'cancel' })
    if(! player.initialized){ player.init((error) = >{
            playError = error
            tryFinish()
        })
    }
    authorizeMovie(movieId, (error, ticket)=>{
        playError = error
        movieTicket = ticket
        tryFinish()
    })
}
Copy the code

As you can see, in the asynchronous programming code above, the state is hard to track: the movieTicket variable appears in three places. So when the project gets complicated, it’s hard to understand how a state is changing.

On the other hand, when using a callback, try… Catch syntax is mostly useless, but using promises can alleviate this problem.

In addition, if you listen to an event and forget to destroy it, you can easily cause a memory leak. Memory leaks are therefore common in asynchronous programming.

Iterator and Observer

To solve these problems, let’s go back to 1994. There was a book in 1994 called Design Patterns

This book talks a lot about programming routines (programming routines are design patterns)

Today we’ll focus on just two of these design patterns

  1. The Iterator Iterator
  2. The Observer observers

The iterator

function makeIterator(array){
    var nextIndex = 0;

    return {
       next: function(){
           return nextIndex < array.length ?
               {value: array[nextIndex++], done: false}, {done: true}; }}; }var it = makeIterator(['a'.'b']);
console.log(it.next().value); // 'a'
console.log(it.next().value); // 'b'
console.log(it.next().done);  // true
Copy the code

ES 6 provides a syntactic sugar to implement the iterator pattern, called a Generator.

function* idMaker() {
  var index = 0;
  while(true)
    yield index++;
}

var gen = idMaker();

console.log(gen.next().value); / / 0
console.log(gen.next().value); / / 1
console.log(gen.next().value); / / 2
Copy the code

Iterator mode means that you can use the.next() API to access the next item “in sequence.” (Next is just a function name and can be used arbitrarily.)

  1. If you have the next term, you get{value: value of the next item, done: false}
  2. If you don’t have the next term, you get{value: null, done: true}

Observer model

The pattern is to listen for changes to an object, and when the object changes, call the function you provide. Object.observe() has been deprecated. Please use Proxy API instead.

var user = {
  id: 0.name: 'Brendan Eich'.title: 'Mr.'
};

// Create the user greeting
function updateGreeting() {
  user.greeting = 'Hello, ' + user.title + ' ' + user.name + '! ';
}
updateGreeting();

Object.observe(user, function(changes) {
  changes.forEach(function(change) {
    // Updates the greeting when the name or title attributes change
    if (change.name === 'name' || change.name === 'title') { updateGreeting(); }}); });Copy the code

The difference between the two models

Given that A is an iterator, B can actively use a.ext () to ask A to change. (B asks A to change) Suppose B is an observer, observing A, then A will actively inform B of any change in A. (B passively receives notification after A changes)

Or put it this way: in observer mode, the observed person iterates over the observer (calling a function of the observer). Just to be clear: an observer is an iterator, and when the observed person changes, one of the observer’s functions is called.

user .on change
    observer.next()
Copy the code

Only, the observer can always.next(), never end. The iterator will terminate, returning {done: true}.

Array V.S. Event

Array: [ {x:1,y:1}, {x:2, y:2}, {x:10,y:10} ]
Event: {x:1,y:1} ... {x:2, y:2} ... {x:10, y:10}
Copy the code

What’s the difference between arrays and events?

They are all collections.

To illustrate the similarities between the two, let’s take two examples.

We’ll start with four operations on Array:

There are some amazing things that we can do with these apis, and at Netflix we basically show people great shows.

We need to show the highest rated episodes to users. Can we do this with the above operation?

let getTopRatedFilms = user= > 
    user.videoLists
        .map( videoList= > 
            videoList.videos
                .filter( video= > video.rating === 5.0)
        ).concatAll()

getTopRatedFilms(currentUser)
    .forEach(film= > console.log(film) )

Copy the code

Now, would you believe me if I told you that a drag operation could be implemented using similar code? You must be thinking to yourself: This can’t be!

It’s time to show the real thing:

let getElemenetDrags = el= >
    el.mouseDowns
        .map( mouseDown= > 
            document.mouseMoves
                .takeUntil(document.mouseUps)
        )
        .concatAll()

getElementDrags(div)
    .forEach(position= > img.position = position )
Copy the code

It all happens because of an Observable.

Observable

Observable = Collections + Time

use

Observable can say

  • The event
  • Data request
  • animation

And you can easily combine these three things, so asynchronous operations are easy.

Converting events to an Observable API is simple

var mouseDowns = Observable.fromEvent(element, 'mouseDown')
Copy the code

How did we manipulate events before? — Listen (or subscribe)

/ / subscribe or listen let handler = e = > console. The log (e) the document. The addEventListener (" mousemove ", Handler) / / unsubscribe or remove to monitor the document. The removeEventListener (' mousemove ', handler)Copy the code

Now how do we manipulate events? – the forEach

// Subscribe to let subscription = mousemoves.foreach (e => console.log(e))Copy the code

Event packaged into observables object, you can easily use the forEach/map/filter/takeUntil/concatAll API to operation, much easier than before.

ForEach can also accept two additional parameters to handle failure cases:

Looks a little like Promise, doesn’t it?

To clearly explains how to use forEach/map/filter/takeUntil/concatAll API to manipulate the observables object, I now inventing a new grammar:

The rules of this grammar are

  1. {1… 2} means that this object will emit a 1 at first and a 2 after some time
  2. {1… 2… 3} means launch 1, launch 2 after some time, launch 3 after two time (i.e…. Indicates a period of time,…… Two periods of time

forEach

> {1... 2... 3}. ForEach (console.log) 1 After a period of time 2 After a period of time 3Copy the code

map

> {1... 2... Map (x=>x+1) 2 After a period of time 3 After a period of time 4Copy the code

filter

> {1... 2... Filter (x=>x>1) After a Period of time 2 After a period of time 3 after a period of time 3Copy the code

concatAll

Consider race conditions (race issues)

{... {1}... {2... 3}... {}... {4} }.concatAll() { ... 1... 2... 3... 4}Copy the code

takeUntil

> {... 1... 2... 3}.takeUntil( {............ {4})... 1... 2... }Copy the code

This API gives us an idea: we don’t need to unsubscribe at all, just tell the system how long the subscription ends; When another event occurs, the system automatically unsubscribes to the previous event.

In fact, many people will not unsubscribe to the point of memory leakage. For example, we often listen for the onload event of the Window, but hardly anyone listens for the onload event of the window.

I haven’t done a wiretap in five years. But my code does not leak memory because:

Back to our drag code:

let getElemenetDrags = el =>
    el.mouseDowns
        .map( mouseDown => 
            document.mouseMoves
                .takeUntil(document.mouseUps)
        )
        .concatAll()

getElementDrags(div)
    .forEach(position => img.position = position )
Copy the code

Second example

There are two difficulties with this demo:

  1. If the user types abcdef in sequence, how many requests should you send? The answer is to make a request with a function.
  2. If the user typed a and then typed B 300 milliseconds later, you would send two requests, one for hot words related to A and one for hot words related to AB. Can you guarantee the order of the two requests? The answer is no. (Race problem)

Think about this in Terms of Observables

Let search = keydempresses. Debounce (250) // The original is throttle, but I personally think the original is wrong, I have asked the speaker on Twitter, .map(key => getJSON('/search? Q =' + input.value).retry(3).takeUntil(keyPresses)).concatall () search.foreach () results => updateUI(results), error => showMessage(error) )Copy the code

The initial callback hell

Finally, we return to the original code

function play(movieId, cancelButton, callback){ let movieTicket let playError let tryFinish = () =>{ if(playError){ callback(null, playError) }else if(movieTicket && player.initialized){ callback(null, movieTicket) } } cancelButton.addEventListener('click', ()=>{ playError = 'cancel' }) if(! player.initialized){ player.init((error)=>{ playError = error tryFinish() }) } authorizeMovie(movieId, (error, ticket)=>{ playError = error movieTicket = ticket tryFinish() }) }Copy the code

By changing the way you think, you can write code like this

let authorizations = 
    player.init()
        .map((a)= >
            playAttempts
                .map(movieId= >
                    player.authorize(movieId)
                        .retry(3)
                        .takeUntil(cancels)
                )
                .concatAll()
        )
        .concatAll()

authorizations.forEach(
    license= > player.play(license),
    error => showError(error)
)
Copy the code

conclusion

That’s the idea behind rx.js. If you need a more practical exercise, you can click on the link below:

  • Reactivex. IO/Learnrx /
  • Chinese course: www.google.com/search?q=si… (I recommend the rx.js tutorial written by Tai Wolf on Zhihu)

The original video was extremely fast and I listened to it several times. This lesson is my translation and understanding of the speech.

I think this video is so friendly to rx.js novices that they can basically understand it once they listen to it, so I spent a day in Chinese translation and re-interpretation.