The background,

Understanding the concept of Observable is a critical part of learning Rx programming, and it often takes a lot of “hitting the wall” to get to enlightenment. It’s a bit like learning to ride a bike when you were a kid and you had to fall a few times to master it. Of course, if there is a way to “talk”, you can take fewer detours and understand the subtlety of Rx as soon as possible.

Second, the observables

An Observable is a “data source” or “event source” that has the ability to be observed, as opposed to actively harvesting the data. An Observable is like a tap, and you can turn on the tap — subscribe to An Observable, and water — and the data will flow out. This is the core idea of responsive programming — turning the active into the passive. But that is beyond the scope of this article.

(Image from the Internet)

An Observable is a concept that can be implemented in different ways. This article uses higher-order functions to implement two common Observables: fromEvent and Interval. The behavior of subscribing to and unsubscribing to an Observable helps readers understand what an Observable is.

Higher order functions

The concept of higher-order functions comes from functional programming. The simple definition is that the input or return value of a function is a function of a function. Such as:

function foo(arg){
    return function(){
        console.log(arg)
    }
}
constBar = foo(" hello world ") bar()// hello world
Copy the code

Ps: Higher-order functions can do a lot of things, but they are only used for the purposes of this article.

The above call to foo does not print the Hello world directly; it simply caches the hello world. We then call the returned bar function as needed and actually do the job of printing hello World.

Why do we do this? The effect of this is, in effect, to “delay” the call. And the essence of it all is in the word “delay.” We’re actually packaging a behavior to look like something consistent, like a delivery box.

(Image from the Internet)

There can be different things in it, but for logistics it’s the same thing. Therefore, it is possible to form a unified operation of the express box, such as stacking, transporting, storing, and even opening the box is the same action.

Going back to the previous example, calling foo is equivalent to packing a delivery box, which has a fixed program that performs a print operation when the delivery box is opened (calling bar).

We can have foo1, foo2, foo3… There are all kinds of programs in there, but all of these foos have one common action: “open.” (Provided that foo returns a function so that the “open” operation is satisfied, that is, calling the returned function).

function foo1(arg){
    return function(){
       console.log(arg+"?")}}function foo2(arg){
      return function(){
         console.log(arg+"!")}}constBar1 = foo1 (" hello world ")const bar2 = foo2("yes")
bar1()+bar2() // hello world? yes!
Copy the code

Iv. Express box model

4.1 Express box model 1: fromEvent

With that in mind, let’s take a look at one of the most common Observables in Rx programming — fromEvent(…) . For beginners in Rx programming, fromEvent(…) can be difficult to understand at first. And addEventListener (…) What’s the difference.

btn.addEventListener("click",callback)
rx.fromEvent(btn,"click").subscribe(callback)
Copy the code

If you execute the code directly, the effect is exactly the same. So what’s the difference? The most immediate difference is that the subscribe function works on fromEvent(…) The addEventListener is applied directly to the BTN. The subscribe function is some kind of “open” operation, while fromEvent(…) It’s some kind of delivery box.

FromEvent is actually a “delayed” call to addEventListener

function fromEvent(target,evtName){
    return function(callback){
        target.addEventListener(evtName,callback)
    }
}
const ob = fromEvent(btn,"click")
ob(console.log)// Is equivalent to subscribe
Copy the code

Oh! So fromEvent is essentially a higher-order function

How to subscribe to the “open” operation is beyond the scope of this article. In Rx programming, this subscribe action is called “subscribe.” Subscription is a uniform operation for all Observables. Again, the “call” to An Observable in this article is the logical equivalent of a subscribe.

Here is another example, basically can let the reader give two anti N.

4.2 Express Box Model 2: interval

Rx has an interval, what’s the difference between that and setInterval?

Interval is a delayed call to setInterval! Bingo!

function interval(period){
    let i = 0
    return function(callback){
        setInterval(period,()=>callback(i++))
    }
}
const ob = interval(1000)
ob(console.log)// Is equivalent to subscribe
Copy the code

From the above two examples, either fromEvent(…) Or the Interval (…) The internal logic is completely different, but they’re all part of an Observable, a delivery box called an Observable.

FromEvent (BTN,”click”), Interval (1000), etc…

5. Advanced express box

With the above foundation, the next step: we have so many express boxes, so we can repackage these express boxes.

At the beginning of the article, the express box has unified some operations, so we can stack many express boxes together, that is, combined into a big express box! The large delivery box has the same “open” operation (i.e., subscribe) as the small delivery box. What happens when we open this big delivery box?

There are many different possibilities, such as opening small boxes one by one (Concat), opening all small boxes at once (Merge), or opening only the easiest box to open (RACE).

Here is a simplified version of the merge method:

function merge(... obs){
    return function(callback){
        obs.forEach(ob=>ob(callback)) // Open all the boxes}}Copy the code

Let’s take fromEvent and Interval as examples.

Merge two Observables using the merge method:

const ob1 = fromEvent(btn,'click') // Make delivery box 1
const ob2 = interval(1000) // Make delivery box 2
const ob = merge(ob1,ob2) // Make a big delivery box
ob(console.log) // Open the big express box
Copy the code

When we “open” ob, two of the smaller observables will also “open” ob, the logic in either of the smaller observables will be executed, and we will merge the two Observables into one.

That’s why we go to the trouble of encapsulating asynchronous functions into Observable (Observable) — so we can manipulate them all in one way! Of course, just “open” (subscribe) is the most basic function, let’s move on.

6. Destroy the express box

6.1 Destroy the delivery box — Unsubscribe

Let’s use fromEvent as an example. We wrote a simple higher-order function as a wrapper around addEventListener:

function fromEvent(target,evtName){
    return function(callback){
        target.addEventListener(evtName,callback)
    }
}
Copy the code

When we call this function, we generate a delivery box (fromEvent(BTN,’click’)). When we call the function returned by this function, we open the box (fromEvent(BTN,’click’)(console.log)).

So how do we destroy this open delivery box?

First we need to get an open shipping box. The above function call results in void. We can’t do anything, so we need to construct an open shipping box. Again, use the higher-order function idea: return a function inside the returned function for the destruction operation.

function fromEvent(target,evtName){
    return function(callback){
        target.addEventListener(evtName,callback)
        return function(){
            target.removeEventListener(evtName,callback)
        }
    }
}
const ob = fromEvent(btn,'click') // Make a delivery box
const sub = ob(console.log) // Open the box and get a function that can be used to destroy it
sub() // Destroy the delivery box
Copy the code

We can do the same for Interval:

function interval(period){
    let i = 0
    return function(callback){
        let id = setInterval(period,()=>callback(i++))
        return function(){
            clearInterval(id)
        }
    }
}
const ob = interval(1000) // Make a delivery box
const sub = ob(console.log) // Open the express box
sub() // Destroy the delivery box
Copy the code

6.2 Destroy advanced delivery boxes

Let’s use merge as an example:

function merge(... obs){
    return function(callback){
        const subs = obs.map(ob=>ob(callback)) // Subscribe to all destroy functions and collect all destroy functions
        return function(){
            subs.forEach(sub=>sub()) // Iterate over the destruction function and execute}}}const ob1 = fromEvent(btn,'click') // Make delivery box 1
const ob2 = interval(1000) // Make delivery box 2
const ob = merge(ob1,ob2) // Make a big delivery box
const sub = ob(console.log) // Open the big express box
sub() // Destroy the large delivery box
Copy the code

When we destroy the big boxes, we destroy all the little boxes inside.

Six, supplement

This brings us to two important Observable operations (subscribe and unsubscribe). It’s important to note that unsubscribe does not apply to an Observable, but to an “open” delivery box.

In addition, there are two important observables operations that emit events and complete/exception (these are callbacks that are initiated by an Observable and are in the opposite direction of the operations, so they are not really called operations).

We can compare Observable to a tap. Instead of opening the box, we can turn on the tap, and the callback function we pass in can be likened to a water cup! Since callbacks are already familiar, we won’t cover them in this article.

Seven, afterword.

To summarize what we have learned, we have “delayed” some operations through higher-order functions and given uniform behavior, such as “subscribe” to delay the execution of asynchronous functions, and “unsubscribe” to “delay” the execution of resource destruction functions on top of the above.

These so-called “delayed” implementations are the most difficult and central part of Rx programming behind the scenes. The essence of Rx is to encapsulate asynchronous functions and abstract them into four behaviors: subscribe, unsubscribe, issue an event, and complete/exception.

There are many ways to actually implement the Rx library, but this article only uses the idea of higher-order functions to help you understand the nature of Observable. In the official version, the express box is not a higher-order function, but an object, but essentially the same, which leads to a topic: The similarities and differences between functional programming and object-oriented programming are broken down next time.

Author: Vivo Internet Development team -Li Yuxiang