The background,

Understanding the concept of an Observable is crucial in learning RX programming, and it usually takes a lot of “bumping into the wall” to get to “enlightenment” in the normal learning process. 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 “words”, you can take a few detours, as soon as possible to understand the subtley RX.

Second, the observables

Observable literally translates as “Observable.” In other words, it’s a kind of “data source” or “event source.” A data source has the ability to be observed, which is fundamentally different from the way you retrieve your data. The metaphor is that an Observable is like a faucet, and you can turn on the faucet — subscribe to an Observable, and the water — and the data will flow out. This is the core idea of reactive programming — turning the active into the passive. But I won’t go into that in detail in this article.

(Photo courtesy of the Internet)

Observable is a concept that can be implemented in different ways. In this paper, we implement two common Observables: fromEvent and Interval by using higher-order functions. We’ll help you understand what an Observable is by explaining how to subscribe and unsubscribe to an Observable.

3. Higher order functions

The concept of higher-order functions is derived 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)} const bar = foo(" hello world ") bar() // hello world

PS: There are many things that higher-order functions can do, but they are only used for the situations needed in this article.

The call to foo does not print the Hello World directly, but simply caches the Hello World. We then call the returned bar function as needed, and then do the actual printing of Hello World.

Why do this encapsulation? In effect, this “delays” 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.

(Photo courtesy of the Internet)

You can have different things in it, but it’s the same thing for logistics. Therefore, unified operation of the package can be formed, such as stacking, transportation, storage, and even the opening of the box.

Going back to the previous example, calling foo is equivalent to packaging a delivery box with a fixed procedure 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 it, but these foos all have one thing in common: “open”. If this foo returns a function, then the “open” operation, which calls the returned function, is satisfied.

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

IV. Express box model

4.1 Delivery Box Model 1: FromEvent

With that in mind, let’s take a look at one of the most commonly used 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)

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

FromEvent is actually a “deferred” call to the addEventListener

function fromEvent(target,evtName){ return function(callback){ target.addEventListener(evtName,callback) } } const ob = FromEvent (BTN,"click") ob(console.log)// Subscribe

Oh! FromEvent is essentially a higher-order function

How to subscribe to the ‘open’ operation is beyond the scope of this article. In RX programming, the action of subscribing is called ‘subscribe’. “Subscribe” is the uniform operation of all Observables. Again: The “call” to an Observable in this article is logically equivalent to a subscribe.

Let me give you another example, which is basically enough for the reader to give two inverse N.

4.2 Delivery Box Model 2: Interval

There’s an interval in Rx. What’s the difference between it 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)// Equip to subscribe

From the above two examples, whether from FromEvent (…) Or the Interval (…) Although the internal logic is completely different, they are both part of this “delivery box” thing, which we call an Observable.

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

Five, high order express box

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

At the beginning of the article said, the express box unified some operations, so we can put a lot of express boxes stacked together, that is, combined into a big express box! This large package, like the small package, has an “open” operation (that is, a subscription). What happens when we open this big package?

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

Here is a simplified version of the merge method:

function merge(... Obs){return function(callback){obs.foreach (ob=>ob(callback)) // Open all boxes}}

Let’s take the FromEvent and Interval examples.

Merge merge merge merge merge merge merge merge merge merge merge merge

// merge(ob1,ob2) // merge(ob1,ob2) // merge(ob1,ob2) // merge(ob1,ob2) // merge(ob1,ob2) // merge(ob1,ob2) // merge(ob1,ob2 Ob (console.log) // Open the big package

When we “open” the OB box, two of the smaller boxes will be “opened” and the logic in any one of the smaller boxes will be executed and we will merge the two Observables to become one.

That’s why we’re so hard at work packaging asynchronous functions into Observable boxes — so we can manipulate them all in one place! Of course, just “open” (subscribe) is the most basic function, so let’s start the next step.

VI. Destroy the express boxes

6.1 Destroy the Courier Box — Unsubscribe

Let’s take the FromEvent example again. We wrote a simple higher-order function that encapsulates the addEventListener:

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

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

So how do we destroy this open package?

First we need to get an open box. The function call on it results in void. We can’t do anything about it, so we need to construct an open box. Back to the idea of higher-order functions: return a function within 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') / / production delivery box const sub = Ob (console.log) // Open the Courier box and get a function sub() // destroy the Courier box

Similarly, we can do the same for intervals:

function interval(period){ let i = 0 return function(callback){ let id = setInterval(period,()=>callback(i++)) return Function (){clearInterval(id)}} const ob = interval(1000) // const sub = ob(console.log Destroy Courier boxes

6.2 Destroy high-order express boxes

Let’s take the example of merge:

function merge(... Return function(callback){const subs = obs.map(ob=>ob(callback))} return function(){const subs = obs.map(ob=>ob(callback))} Subs.foreach (sub=>sub()) //}} const ob1 = fromEvent(BTN,'click') // // merge(ob1,ob2) // merge(ob1,ob2) // merge(ob1,ob2) // merge(ob1,ob2) // merge(ob1,ob2) // merge(ob1,ob2) // merge(ob1,ob2) //

When we destroy the big package, we destroy all the small packages in it.

Six, supplement

Now that we’ve covered the two important operations of an Observable (subscribe and unsubscribe), it’s important to note that unsubscribe does not apply to an Observable, but to an “open” delivery box (the thing that comes back after subscribing to an Observable)!

In addition to the Observable, there are two other important operations: emit an event and complete/exception. (These two operations are called callbacks initiated by the Observable, which are in the opposite direction of the operation, so they are not called operations.)

We can compare Observable to a faucet. The original opening of the box turns into a faucet. And the callback function we pass in can be compared to a cup of water. Since you are already familiar with callback functions, I won’t cover them in this article.

Seven, afterword.

To sum up what we’ve learned, we’ve “deferred” some operations with higher-order functions and given them uniform behavior. For example, “Subscribe” delays the execution of an asynchronous function and “Unsubscribe” delays the execution of a resource destruction function on top of that.

These so-called “delayed” executions 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, emit events, and complete/exception.

There are many ways to actually implement the Rx library, but this article just uses the idea of higher-order functions to help you understand the nature of Observable. In the official version of the implementation, the delivery box of an Observable is not a higher-order function, but an object, but it is essentially the same. Here’s a topic: The similarities and differences between functional programming and object orientation are explained in the next section.

Authors: Vivo Internet Development Team -Li Yuxiang