What is Reactive programming?

There are a lot of bad explanations and definitions on the Internet. Wikipedia is  as empty and theoretical as ever.  The authoritative answer to Stackoverflow is clearly not suitable for beginners. Reactive Manifesto  looks like what you show to your company’s project manager or boss. Microsoft’s  Rx terminology  “Rx = Observables + LINQ + Schedulers” is too heavy and Microsoft’s taste will only confuse most people. The terms “Reactive” and “Propagation of change” do not convey any meaningful concepts relative to the MV* framework you use and your favorite programming language. The Views layer of the framework will of course react to the Models layer, and the changes will of course propagate. Without these, nothing will be rendered.

So don’t talk about these nonsense anymore.

Reactive Programming is programming with asynchronous data streams

On the one hand, this is not something new. Event buses or Click events are essentially asynchronous event streams that you can listen to and handle. The idea of ​​Reactive programming is roughly as follows: You can create a Data stream with anything including the Click and Hover events. Stream is cheap and common, and anything can be a Stream: variables, user input, properties, Cache, data structures, and more. For example, imagine your Twitter feed is like a Data stream like Click events, you can listen to it and respond accordingly.

On this basis, you also have amazing functions to combine, create, and filter these Streams. This is where functional magic comes in. Stream can accept one or even multiple streams as input. You can combine two Streams, you can also filter out the Events you are interested in from a Stream to generate a new Stream, and you can map the data values ​​in a Stream to a new Stream.

Since Streams are so important in reactive programming, we should understand them well, starting with the familiar “Clicks on a button” Event stream.

Stream is a time-ordered sequence of events that emits three different Events: (some type) Value, Error, or a “Completed” Signal. Consider the timing of “Completed”, for example, when the window or view containing the button is closed.

By defining event handlers for Value, Error, and “Completed”, we will capture these events asynchronously. Sometimes you can ignore Error and “Completed”, you only need to define the event handler for Value. Listening for a Stream is also called a subscription, and the function we define is the observer, and Stream is the observer, which is actually the  Observer Design Pattern .

The above diagram can also be redrawn as ASCII using ASCII. We will use this diagram in the following sections:

    --a---b-c---d---X---|->

    a, b, c, d are emitted values
    X is an error
    | is the 'completed' signal
    ---> is the timeline 

Now that you’re familiar with reactive programming, in order not to make you bored, we can try something new: we’ll turn a Click event stream into a new Click event stream.

First, let’s make a counter that records how many times a button has been clicked. In a common reactive programming library, each Stream has multiple methods, such as  map,  filter,  scan, and so on. When you call one of the methods, for example  clickStream.map(f), it will return a new Stream based on the original Click stream. It does not make any changes to the original Click steam. This feature is called immutability, and it works for reactive programming, if the juice is for pancakes. We can also chain the methods, such as  clickStream.map(f).scan(g):

    clickStream: ---c----c--c----c------c-->
               vvvvv map(c becomes 1) vvvv
               ---1----1--1----1------1-->
               vvvvvvvvv scan(+) vvvvvvvvv
    counterStream: ---1----2--3----4------5--> 

map(f) The f values ​​in the original Stream are mapped to the new Stream according to the function you provide  . In our case, we map each Click to a number 1. scan(g) According to the g function you provide,  all the Values ​​in the Stream are aggregated into a Value  x = g(accumulated, current) . In this example, it  g is just a simple addition function. Then, each time  counterStream Click, the total number of clicks is sent to its observer.

To demonstrate the true power of reactive programming, let’s assume that you want a Stream that contains a “double-click” event. To make it even more interesting, let’s assume that the Stream we want is to consider triple clicks at the same time, or more broadly, combos (twice or more). Take a deep breath and imagine how you would achieve it in a traditional imperative and stateful way. I bet the code will be a mess, and will use some variables to save the state, as well as some code to calculate the time interval.

In reactive programming, the implementation of this feature is very simple. In fact, this logic has only  4 lines of code . But now we don’t care about the code. Thinking in a chart is the best way to understand how to build a stream, whether you are a beginner or an expert.

The gray box is used to convert the Stream function. First, in a nutshell, we’ve accumulated Clicks in a row for 250 ms into a list (that’s buffer(stream.throttle(250ms) what we’re doing. Don’t worry about these details, we’re just showing reactive programming). The result is a list of Streams, and then we use  map() to map each list to an integer, ie its length. In the end, we use  filter(x >= 2) to filter out the integer 1. In this way, 3 operations generate the Stream we want. Then we can subscribe (“listen”) to this Stream and react in the way we want it to.

I hope that you can feel the beauty of this example. This example is just the tip of the iceberg: you can apply the same operation to different kinds of Streams, for example, a Stream of API responses; on the other hand, there are many other functions available.

Why should I use Reactive Programming (RP)

Reactive programming increases the level of abstraction of your code, so you can focus on those interdependent events that define your business logic, rather than entangled with a lot of implementation details. The code for RP tends to be more concise.

Especially when developing these highly interactive Webapps and mobile apps with a lot of UI events related to data events, the advantages of RP are even more obvious. Ten years ago, the interaction of a web page was just to submit a long form to the back end, but only a simple rendering on the front end. Apps are more real-time: modifying a form field automatically saves the modified value to the backend, and when it “likes” something, it reacts to other online users in real time.

Today’s Apps have a large variety of real-time Events to give users a more interactive experience. We need tools to deal with this change, and reactive programming is an answer.

Example of thinking in Reactive Programming (RP)  way

Let us do some practice. A real-world example guides us step by step in thinking about RP. It is not a fictitious example, nor does it explain only half of the concept. After completing the tutorial, we will write the code that is actually available, and we will know what it is and know why.

In this tutorial, I will use JavaScript and  RxJS  as a tool, because JavaScript is the most popular language now, and the  Rx* library family is  available in multiple languages ​​and supports multiple platforms ( .NET ,  Java , Scala, Clojure). ,  JavaScript ,  Ruby ,  Python ,  C++ ,  Objective-C/Cocoa , Groovy, etc.). So, no matter what tool you use, you can benefit from the tutorial below.

Implement the Reactive Programming (RP) “Who to follow” recommendation interface

On Twitter, this means that the UI elements of other accounts look like this:

We will focus on simulating its core features as follows:

  • Load account data from the API at startup and display 3 recommendations
  • When clicking “Refresh”, load 3 other recommended users into these three lines.
  • When you click the ‘x’ button in the row of the account, only the one recommendation is cleared and a new recommendation is displayed.
  • Each line shows the avatar of the account and a link to their home page.

We can ignore other features and buttons because they are secondary. At the same time, because Twitter recently closed the API for unauthorized users, we will implement this recommendation interface for Github, not Twitter. This is Github’s API for getting users .

Reactive Programming (RP) way Request and response

What should you do with this problem in Rx? Ok, first of all, (almost) everything can be converted to a Stream. This is the spell of Rx. Let’s start with the simplest feature: “Loading 3 account data from the API at startup”. This is nothing special, it is simply (1) making a request, (2) receiving a response, and (3) rendering the response. So let’s go ahead and use Stream to represent our request. At first, you may feel like killing a chicken, but we should start from the most basic, right?

At startup, we only need to make a request, so if we turn it into a Data stream, it is a Stream with only one Value. Later, we know that there will be multiple requests, but for now, there is only one request.

    --a------|->

    Where a is the string 'https://api.github.com/users' 

This is a Stream of the URL we want to make a request to. Whenever a request event occurs, it tells us two things: “when” and “what”. “When” this request will be executed, when this event will be mapped. “What” will be requested, this is the mapped value: a String containing the URL.

In RX  , creating a Stream with only one value is very simple. The official called a Stream “Observable” because it can be observed, but I found it to be a stupid name, so I called it  Stream*.

 var requestStream = Rx.Observable.just('https://api.github.com/users'); 

But now, that’s just a Stream that contains a String, and there are no other operations, so we need to somehow have that value mapped. It is through subscribing  this Stream.

    requestStream.subscribe(function(requestUrl) {
    // execute the request
    jQuery.getJSON(requestUrl, function(responseData) {
        // ...
    });
    }

Keep in mind that we’re using jQuery’s Ajax functions ( we’m assuming you already  know already ) to handle asynchronous request operations. But wait, Rx can be used to handle asynchronous data streams. Can the response to this request be treated as a Stream containing the data that will arrive? Of course, in theory, it should be ok, so let’s try it.

    requestStream.subscribe(function(requestUrl) {
    // execute the request
    var responseStream = Rx.Observable.create(function (observer) {
        jQuery.getJSON(requestUrl)
        .done(function(response) { observer.onNext(response); })
        .fail(function(jqXHR, status, error) { observer.onError(error); })
        .always(function() { observer.onCompleted(); });
    });

    responseStream.subscribe(function(response) {
        // do something with the response
    });
    } 

Rx.Observable.create()The thing to do is  to create your own Stream by explicitly notifying each Observer (or “Subscriber”) Data events( onNext() ) or Errors (  onError()). All we do is wrap up jQuery Ajax Promise. Excuse me, this means that Promise is essentially an Observable?

Yes.

Observable is Promise++. In Rx, you can  var stream = Rx.Observable.fromPromise(promise) easily turn a Promise into an Observable, so let’s do it. The only difference is that the Observable does not follow  Promises/A+ , but there is no conflict in concept. Promise is an Observable with only one mapped value. Rx Stream goes a step further than Promise to allow multiple values ​​to be returned.

This is great, and shows that Observables are at least as powerful as Promise. So if you believe in the things that Promise promotes, then please also pay attention to what Rx Observables can do.

Now back to our example, if you’ve noticed in our  subscribe() inner turn calls another  subscribe() , similar Callback hell. Again, you should also notice that it  responseStream is built on  requestStream top. As you learned before, there are simple mechanisms in Rx that can be converted from other Streams and create new Streams, so we should do the same.

One of the basic functions you need to know now is  [map(f)](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md#rxobservableprototypemapselector-thisarg) that it  f() applies to each value in Stream A and puts the returned value into Stream B. If we also do the same for the request Stream and the response Stream, we can map the Request URL to a response Promise (and Promise can be converted to Streams).

    var responseMetastream = requestStream
    .map(function(requestUrl) {
     return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
      });

Then we will create a monster called “Metastream”: a Stream containing a Stream. Don’t be afraid for the time being. A Metastream is a Stream where the value of the map is another Stream. You can think of it as  pointers : the value of each map is a pointer to another Stream. In our case, each request URL is mapped to a pointer to the response Promise stream.

Response’s Metastream looks confusing and doesn’t seem to help us. We only want a simple response stream where the value of each map should be a JSON object, not a ‘Promise’ of a JSON object. It’s time to introduce (Mr. Flatmap) ( https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md#rxobservableprototypeflatmapselector-resultselector ): it  map() ‘s a version, passed Apply all operations applied to the “trunk” Stream to the “branch” Stream, which can be “flatten” Metastream. Flatmap is not intended to “fix” Metastream, because Metastream is not a vulnerability, it is just a tool for handling asynchronous responses in Rx.

    var responseStream = requestStream
    .flatMap(function(requestUrl) {
        return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
      });

well. Since the response stream is defined according to the request stream, if we later make more requests on the request stream, we will get the corresponding response event on the response stream, as expected:

    requestStream:  --a-----b--c------------|->
    responseStream: -----A--------B-----C---|->

    (lowercase is a request, uppercase is its response) 

Now, we finally have a response stream, so we can render the received data:

    responseStream.subscribe(function(response) {
    // render `response` to the DOM however you wish
    }); 

Putting all the code so far together is like this:

    var requestStream = Rx.Observable.just('https://api.github.com/users');

    var responseStream = requestStream
    .flatMap(function(requestUrl) {
        return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
    });

    responseStream.subscribe(function(response) {
    // render `response` to the DOM however you wish
    }); 

Reactive Programming (RP) Refresh button

I didn’t mention that the returned JSON was a list with 100 user data. Because this API only allows us to set the offset and cannot set the number of users returned, we are now using only 3 user data and wasting another 97 data. This problem can be ignored for the time being, and we will learn how to cache this data later.

Each time the refresh button is clicked, the request stream will map a new URL and we will get a new response. We need two things: one is to refresh the button composed of Click events on the button (the spell: everything can be Stream), and we need to change the request stream according to the refresh click stream. Fortunately, RxJS provides functions for generating Observables from Event listeners.

    var refreshButton = document.querySelector('.refresh');
    var refreshClickStream = Rx.Observable.fromEvent(refreshButton, 'click'); 

Since refreshing the click event itself does not provide any API URLs to request, we need to map each click to an actual URL. Now we change the refresh click stream to the new request stream, where each Click is mapped to an API endpoint with a random offset.

    var requestStream = refreshClickStream
    .map(function() {
        var randomOffset = Math.floor(Math.random()*500);
        return 'https://api.github.com/users?since=' + randomOffset;
      }); 

Because I was stupid and didn’t use automated testing, I just ruined a feature I had done before. No more requests will be issued at startup, but only when the refresh button is clicked. Amount… I need both of these behaviors: either make a request when I click the refresh button or just open the page.

We know how to generate a Stream for each of these two cases:

    var requestOnRefreshStream = refreshClickStream
    .map(function() {
        var randomOffset = Math.floor(Math.random()*500);
        return 'https://api.github.com/users?since=' + randomOffset;
    });

    var startupRequestStream = Rx.Observable.just('https://api.github.com/users'); 

But how can we “combine” these two into one? Ok, there are  merge() functions. This is an illustration of what it does:

    stream A: ---a--------e-----o----->
    stream B: -----B---C-----D-------->
          vvvvvvvvv merge vvvvvvvvv
          ---a-B---C--e--D--o-----> 

This is simple:

    var requestOnRefreshStream = refreshClickStream
    .map(function() {
        var randomOffset = Math.floor(Math.random()*500);
        return 'https://api.github.com/users?since=' + randomOffset;
    });

    var startupRequestStream = Rx.Observable.just('https://api.github.com/users');

    var requestStream = Rx.Observable.merge(
    requestOnRefreshStream, startupRequestStream
    ); 

There is also a more concise alternative that does not require the use of intermediate variables.

    var requestStream = refreshClickStream
    .map(function() {
        var randomOffset = Math.floor(Math.random()*500);
        return 'https://api.github.com/users?since=' + randomOffset;
    })
      .merge(Rx.Observable.just('https://api.github.com/users')); 

It can even be shorter and more readable:

    var requestStream = refreshClickStream
    .map(function() {
        var randomOffset = Math.floor(Math.random()*500);
        return 'https://api.github.com/users?since=' + randomOffset;
    })
      .startWith('https://api.github.com/users');

startWith() The function does exactly the same thing you expected. Regardless of the Stream you type, the startWith(x) output Stream is initially x. But not enough  DRY , I repeated the API terminal string. One way to fix it is to remove the  refreshClickStream last one startWith() and “simulate” the Click once at the beginning.

var requestStream = refreshClickStream.startWith('startup click')
  .map(function() {
    var randomOffset = Math.floor(Math.random()*500);
    return 'https://api.github.com/users?since=' + randomOffset;
  });

well. If you compare the code of my “destroyed version” to the current one, you will find that the only difference is the addition of a  startWith() function.

Reactive Programming (RP) example and tutorial: Building three recommendations with Stream

So far, we’ve only talked about the  rendering steps that this recommended  UI element performs subscribe()inside the responeStream  . For the refresh button, we have another problem: when you click ‘Refresh’, the three existing recommendations will not be cleared. The new recommendation will appear after the response arrives. In order for the UI to look comfortable, we need to clean up the current recommendations when clicking Refresh.

    refreshClickStream.subscribe(function() {
    // clear the 3 suggestion DOM elements 
    });

No, don’t be so fast, friend. This is not good, we now have two subscribers that affect the recommended DOM elements (the other one is responseStream.subscribe() ), and this is completely inconsistent with  Separation of concerns . Remember the reactive programming spell?

So let’s design the recommended recommendations as a stream, where each mapped value is a JSON object containing the recommended content. We use this to separate the three recommendations. Now the first recommendation looks like this:

    var suggestion1Stream = responseStream
    .map(function(listUsers) {
        // get one random user from the list
        return listUsers[Math.floor(Math.random()*listUsers.length)];
      }); 

Others,  suggestion2Stream and   code that suggestion3Stream can be simply copied  suggestion1Stream. This is not DRY, it will make our example a bit simpler, plus I think this is a good practice that can help consider how to reduce duplication.

We are not processing the rendering in the subscribe() of the responseStream, we do this:

    suggestion1Stream.subscribe(function(suggestion) {
    // render the 1st suggestion to the DOM
    }); 

Going back to “When refreshing, clean up the current recommendations”, we can simply map the refresh clicks to  nulland  suggestion1Stream include them in, as follows:

    var suggestion1Stream = responseStream
    .map(function(listUsers) {
        // get one random user from the list
        return listUsers[Math.floor(Math.random()*listUsers.length)];
    })
    .merge(
        refreshClickStream.map(function(){ return null; })
      );

When rendering, it is null interpreted as “no data”, so hide the UI elements.

    suggestion1Stream.subscribe(function(suggestion) {
    if (suggestion === null) {
        // hide the first suggestion DOM element
    }
    else {
        // show the first suggestion DOM element
        // and render the data
    }
    }); 

The current schematic:

    refreshClickStream: ----------o--------o---->
    requestStream: -r--------r--------r---->
    responseStream: ----R---------R------R-->   
    suggestion1Stream: ----s-----N---s----N-s-->
    suggestion2Stream: ----q-----N---q----N-q-->
    suggestion3Stream: ----t-----N---t----N-t--> 

Which N represents null

As a complement, we can also render “empty” recommendations at the beginning. This is done by  startWith(null) adding it to the Suggestion stream:

    .map(function(listUsers) {
        // get one random user from the list
        return listUsers[Math.floor(Math.random()*listUsers.length)];
    })
    .merge(
        refreshClickStream.map(function(){ return null; })
    )
      .startWith(null); 

The result now is:

    refreshClickStream: ----------o---------o---->
     requestStream: -r--------r---------r---->
    responseStream: ----R----------R------R-->   
    suggestion1Stream: -N--s-----N----s----N-s-->
    suggestion2Stream: -N--q-----N----q----N-q-->
    suggestion3Stream: -N--t-----N----t----N-t--> 

Reactive Programming (RP) example and tutorial:Turn off recommendations and use cached responses

There is another feature that needs to be implemented. For each recommendation, you should have your own “X” button to close it and then load another recommendation at that location. The original idea was to initiate a new request when clicking any of the close buttons:

    var close1Button = document.querySelector('.close1');
    var close1ClickStream = Rx.Observable.fromEvent(close1Button, 'click');
    // and the same for close2Button and close3Button

    var requestStream = refreshClickStream.startWith('startup click')
    .merge(close1ClickStream) // we added this
    .map(function() {
        var randomOffset = Math.floor(Math.random()*500);
        return 'https://api.github.com/users?since=' + randomOffset;
      }); 

This has no effect. This will close and reload  all  the recommendations, not just the one we clicked on. There are some different ways to solve it and make it more interesting, we can fix it by multiplexing the previous requests. The API’s response page has 100 users, and we only use three of them, so there is a lot of new data to use, no need to re-initiate the request.

Similarly, we use Stream to think. When clicking ‘close1’, we want to responseStream get a random user from the response list with the  most recent mapping, such as:

    requestStream: --r--------------->
    responseStream: ------R----------->
    close1ClickStream: ------------c----->
    suggestion1Stream: ------s-----s-----> 

In Rx*, what is called a connector function  combineLatest seems to implement the functionality we want. It accepts two Streams, A and B as input. When one of the Streams emits a value, it  combineLatest takes the last two emitted values ​​a and b from their respective Streams and returns one  c = f(x,y) , which  f is the function defined for you. Use graphs to indicate better:

    stream A: --a-----------e--------i-------->
    stream B: -----b----c--------d-------q---->
          vvvvvvvv combineLatest(f) vvvvvvv
          ----AB---AC--EC---ED--ID--IQ---->

    where f is the uppercase function 

We can  close1ClickStream and  responseStream use combineLatest () on, so no matter what time, when a button is clicked, we can get the latest response emission values, and  suggestion1Stream on to produce a new value. On the other hand, combineLatest() is symmetrical. When a new response is  responseStream fired, it will merge the last ‘close 1’ click events together to produce a new recommendation. This is interesting because it allows us to suggestion1Stream simplify the previous  code to look like this:

    var suggestion1Stream = close1ClickStream
    .combineLatest(responseStream,             
        function(click, listUsers) {
        return listUsers[Math.floor(Math.random()*listUsers.length)];
        }
    )
    .merge(
        refreshClickStream.map(function(){ return null; })
    )
      .startWith(null); 

There is another problem that needs to be solved. combineLatest() uses the two most recent data sources, but combineLatest() cannot generate a Data event in the Output stream when one of the sources does not raise any events. From the ASCII diagram above, you can see that when the first Stream emits a value  a , there is no output generated by this value, and b only the value is output when the second Stream emits a value  .

There are several ways to solve this problem, we choose the simplest one, initially simulate a click event on the ‘close 1’ button:

    var suggestion1Stream = close1ClickStream.startWith('startup click') // we added this
    .combineLatest(responseStream,             
        function(click, listUsers) {l
         return listUsers[Math.floor(Math.random()*listUsers.length)];
        }
    )
    .merge(
        refreshClickStream.map(function(){ return null; })
    )
      .startWith(null); 

Reactive Programming (RP) example and tutorial end

It’s finally done, all the code together is like this:

   var refreshButton = document.querySelector('.refresh');
    var refreshClickStream = Rx.Observable.fromEvent(refreshButton, 'click');

    var closeButton1 = document.querySelector('.close1');
    var close1ClickStream = Rx.Observable.fromEvent(closeButton1, 'click');
    // and the same logic for close2 and close3

    var requestStream = refreshClickStream.startWith('startup click')
    .map(function() {
        var randomOffset = Math.floor(Math.random()*500);
        return 'https://api.github.com/users?since=' + randomOffset;
    });

    var responseStream = requestStream
    .flatMap(function (requestUrl) {
        return Rx.Observable.fromPromise($.ajax({url: requestUrl}));
    });

    var suggestion1Stream = close1ClickStream.startWith('startup click')
    .combineLatest(responseStream,             
        function(click, listUsers) {
        return listUsers[Math.floor(Math.random()*listUsers.length)];
        }
    )
    .merge(
        refreshClickStream.map(function(){ return null; })
    )
    .startWith(null);
    // and the same logic for suggestion2Stream and suggestion3Stream

    suggestion1Stream.subscribe(function(suggestion) {
    if (suggestion === null) {
        // hide the first suggestion DOM element
    }
    else {
        // show the first suggestion DOM element
        // and render the data
    }
     }); 

Although this code is short, it implements a lot of functions: it uses Separation of concerns to manage multiple events and even caches responses. The functional style makes the code look more Declarative than Imperative: we don’t give a set of instructions to execute, but define what this is by defining the relationship between the Streams. For example, we use Rx to tell the computer  *suggestion1Stream* that the ‘close 1’ Stream is merged with a user in the latest response, either when the program is just running or when it is refreshed  null .

Look at the code and does not appear as  if ,  for ,  while such control statements, or general JavaScript applications typical flow-based control callback. If you want to use  filter() , the above  subscribe() may not even have  if , else (implementation details left to the reader as an exercise). In Rx, we have  Stream functions like  map ,  filter ,  scan ,  merge ,  combineLatest ,  startWith, and even more similar functions to control an event-driven program. This tool set allows you to implement more features with less code.

What happens next to Reactive Programming (RP)?

If you think Rx* will be your preferred reactive programming library, take the time to familiarize yourself with this big list of functions , which includes how to convert, merge, and create Observables. If you want to understand these functions through charts, see  RxJava’s very useful documentation with marble diagrams . Whenever you encounter problems, draw these diagrams, think about it, take a look at this bunch of functions, and then continue thinking. In my personal experience, this effect is obvious.

Once you start  programming with Rx , it’s important to understand   the concepts in Cold vs Hot Observables . If you ignore this, you will be pitted if you accidentally. I reminded you. Improve your skills by learning real functional programming and become familiar with  issues that affect Rx , such as side effects.

But reactive programming is more than just Rx . There is also a relatively easy to understand  Bacon.js , which has no Rx  quirks. Elm Language  supports RP in its own way: it is a reactive programming language that compiles into Javascript + HTML + CSS and has a  time travelling debugger . superb.

Rx is very useful in Frontend and Apps that need to handle a large number of events. But it can be used not only on the client side, but also on the back end or when interacting with the database. In fact, RxJava is an important component for implementing Netflix’s API server-side concurrency  . Rx is not a framework that can only be used in an application or language. It is essentially a programming paradigm that can be used in developing any Event-driven software.

 

reference: https://www.cnblogs.com/Unknw/p/7230824.html