Introduction and preliminary application of rxjs

Time:2019-11-1

Preface

Rxjs is a powerful reactive programming library, which provides powerful data flow composition and control capabilities, but its learning threshold has been very high. This sharing expects to interpret its use in business from some special perspectives, rather than from the API perspective.

RxJS introduction

Usually, the interpretation of rxjs is something like this. Let’s see what they mean.

  • Reactive
  • Lodash for events
  • Observable
  • Stream-based

What is reactive? A more intuitive comparison is as follows:

For example, there is an addition relationship between the three variables of ABC:

a = b + c

In the traditional way, this is a one-time assignment process, and the call ends once. After B and C change again, a will not change.

In the concept of reactive, what we define is not a one-time assignment process, but a repeatable assignment process, or the relationship between variables:

a: = b + c

After defining this relationship, every time B or C changes, the expression is recalculated. Different libraries or languages may have different implementation mechanisms and different writing methods, but they share the same ideas and describe the linkage between data.

At the front end, we usually have some ways to deal with asynchronous things:

  • Callback
  • Event
  • Promise
  • Generator

Among them, there are two ways to deal with problems, because there are also two requirements:

  • distribute
  • Technological process

When dealing with the requirements of distribution, callback, event or similar subscription publishing mode is more appropriate; when dealing with the requirements of process nature, promise and generator are more appropriate.

In the front-end, especially in the system with complex interaction, rxjs is actually superior to generator, because every common client-side development is based on event programming, and there will be a lot of event handling, and once a large number of events appear in the system to modify multiple parts of the view (multiple locations of the state tree), the distribution relationship will be more.

The advantage of rxjs is that it combines two modes. Each of its observables can subscribe, and the relationship between the observables can reflect the process (note that the control and processing of the process in rxjs is slightly more intuitive than promise, but weaker than generator).

We can treat all input as data flow, for example:

  • User operation
  • Network response
  • timer
  • Worker

Rxjs provides various APIs to create data flows:

  • Single value: of, empty, never
  • Multi value: from
  • Timing: interval, timer
  • Create from event: fromEvent
  • Create from promise: frompromise
  • Custom create: create

The created data stream is an observable sequence, which can be subscribed to or used for some transformation operations, such as:

  • Change data form: map, mapto, plug
  • Filter some values: filter, skip, first, last, take
  • Operations on the timeline: delay, timeout, throttle, debounce, audit, buffertime
  • Accumulation: reduce, scan
  • Exception handling: throw, catch, Retry, finally
  • Conditional execution: takeuntil, delaywhen, retrywhen, subscribeon, observeon
  • Transfer: switch

You can also combine several data flows:

  • Concat, keep the original sequence order to connect two data streams
  • Merge, merge sequence
  • Race, one of the data flows is completed by default
  • Fork join, the preset condition is that all data flows are completed
  • Zip, take the last value of each source data stream and merge it into an object
  • Combine latest, take the last value of each source data stream and merge it into an array

In fact, rxjs has gone too far in event processing. From event to stream, it is called lodash for events, rather than lodash for stream. The operators it provides are comparable to lodash.

The word “data flow” is often translated from data flow, but flow is different from stream. My understanding is that flow only focuses on one general direction, while stream is more strictly constrained, which is more like flowing in an invisible pipeline.

So, what is the shape of the data pipeline?

In rxjs, there are several things:

  • Observable observable sequence
  • Observer, only in and out
  • Subject can be used as an observer, and can be used as an observable sequence
  • Replaysubject with playback
  • Subscription subscription relationship

The first three kinds of things, according to the possibility of their data in and out, can easily understand their connection mode, that is, the so-called “shape” of the pipe. One end is closed, the other end is open, or both ends are open, which can be used to assist memory.

The subscription mentioned above is a subscription relationship formed after subscription, which can be used to unsubscribe.

Next, let’s take a look at some examples of the capabilities provided by rxjs and the transformation of ideas needed to develop with it.

Example 1: simple subscription

In many cases, we will have some time display scenarios, such as adding comments under the page. The comment list shows when they were created. In order to make the meaning clearer, we may introduce a library such as moment to convert this time into the distance from the current time:

const diff = moment(createAt).fromNow()

In this way, the time displayed is: in one minute, yesterday, last month.

However, we have noticed that this transformation is introduced to enhance the experience. If a user stays in the current view for too long, these information will become inaccurate. For example, the user stays for an hour, and the information he sees also shows that he made a comment five minutes ago, and the actual time is an hour and five minutes ago.

From this point of view, we only do half of this experience enhancement, and inaccurate information cannot be counted as enhanced experience.

In the absence of rxjs, we may do this through a timer, such as in the component:


tick() {
this.diff = moment(createAt).fromNow()
setTimeout(tick.bind(this), 1000)
}

But the component does not have to have only one instance. In this way, there may be many timers running at the same time on the whole interface, which is a waste. If you want to optimize, you can make the timer into a service, put in the things that need to be executed periodically in the business, and run it as a timing task.

If you use rxjs, you can easily do this:


Observable.interval(1000).subscribe(() => {
this.diff = moment(createAt).fromNow()
})

Example 2: manipulation of time axis

A very powerful feature of rxjs is that it treats data in the way of flow. Therefore, some operators can be used to delay, sample and adjust the density of all data on the whole flow.


const timeA$ = Observable.interval(1000)
const timeB$ = timeA$.filter(num => {
return (num % 2 != 0)
&& (num % 3 != 0)
&& (num % 5 != 0)
&& (num % 7 != 0)
})
const timeC$ = timeB$.debounceTime(3000)
const timeD$ = timeC$.delay(2000)

In the sample code, we create four flows:

  • A is generated by timer, one value per second
  • B filtered some out of a
  • On the basis of B, C processes every two values within 3 seconds, leaving only the last value
  • D shifts the result of C backward for 2 seconds

So the results are as follows:

A: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
B:    1                                 11       13                  17       19
C:          1                                                  13                             19
D:                 1                                                      13

Example 3: we are late

Rxjs also provides something like behaviorsubject and replaysubject, which are used to record some important information on the data stream, and let those “we’re late” subscribers play back everything they missed before.

Replaysubject can specify the number of reserved values, and the excess will be discarded.

The latest version of the legend of heroes of archery is quite popular. Let’s use code to describe one of the scenes.

Guo Jing and Huang Rong endorsed together. Huang Rong had a good memory. He could remember everything he saw. But Guo Jing, a fish, could only remember the last three words for seven seconds. They recited the nine Yin Scripture together.

The code implementation is as follows:

Const nine Yin Scripture = 'the way of heaven, damage more than make up for less'
Const huangrong $= new replaysubject (number. Max_value)
Const Guo Jing $= new replaysubject (3)
Const reading $= observable. From (nine Yin Scripture. Split (''))
Read $. Subscribe (Huang Rong $)
Book $. Subscribe (Guo Jing $)

After the implementation, we can see that Huang Rong recited all the words, and Guo Jing only remembered the three words of “make up the deficiency”.

Example 4: automatically updated state tree

People familiar with Redux should be familiar with such a set of concepts:

Current view status: = previous status + modified part

After an application is started, the change of the whole global state is equal to the result of state modification caused by all actions after the initial state is superimposed.

So this is a typical reduce operation. In rxjs, there is a scan operator to express this meaning. For example, we can express such a thing:

const action$ = new Subject()
const reducer = (state, payload) => {
//Overlay the payload on the state to return
}
const state$ = action$.scan(reducer)
.startWith({})

Just push the action $into the action $, you can get the current state on the state $.

In Redux, there will be a thing called combinedreducer. When the state is large, use different reducers to modify different branches of the state, and then merge. If rxjs is used, it can also be easily expressed:


const meAction$ = new Subject()
const meReducer = (state, payload) => {}
const articleAction$ = new Subject()
const articleReducer = (state, payload) => {}
const me$ = meAction$.scan(meReducer).startWith({})
const article$ = articleAction$.scan(articleReducer).startWith({})
const state$ = Observable
.zip(
me$,
article$,
(me, article) => {me, article}
)

With this mechanism, we have implemented similar functions of Redux, and there are Redux observable middleware based on rxjs in the community.

Note that in our code, we do not use dispatch action to strictly simulate redux.

After further consideration, in a more complex scenario, reducer is actually very complex. For example, to initiate an operation on a view, you need to modify many parts of the view, so you need to modify different locations of the global state tree.

In such a scenario, for an action initiated from a view, either a very complex reducer is called to change data everywhere, or multiple actions are initiated again to let many reducers change their own data.

The problem of the former is that the code coupling is too serious; the problem of the latter is that the whole process is too difficult to track. For example, it is very difficult to track the status of a certain block and find out where it is changed by the modification initiated from.

If we can regard the synchronous modification process above observable as reducer, we can greatly simplify the code from other perspectives and make the linkage logic clear. For example, if we want to describe the editing rights of an article:


const editable$ = Observable.combineLatest(article$, me$)
.map(arr => {
let [article, me] = arr
return me.isAdmin || article.author === me.id
})

What is the essence of this code? In fact, it is still a reducer in essence, which represents the process of data merging and transformation, and it is synchronous. We can reduce the changes of article and me to Article $and me $, and they distribute implicit actions to push editable to calculate new values.

For more detailed exploration, please refer to the previous article: data layer design of complex single page applications

Summary

This article introduces the usage scenario of rxjs through some simple examples, which can be described in this sentence:

It is simple, broad in meaning, abstruse and interesting

Rxjs provides a large number of operators to handle different business requirements. For the same scenario, there are many possible implementations that need to be considered carefully before writing code. Because rxjs has a high degree of abstraction, it can express very complex meanings in very short code, which requires developers to have strong inductive ability.

This article is the first time to share with others and focus on science popularization after entering the ant financial service. Some further discussions may be made later.

Most of ant’s business system front-end is not suitable for rxjs, most of which are middle and back-end crud systems, because of two reasons: the requirements of integrity and real-time are not high.

What is integrity? This is a concept of system design. Many business modules in the system are not isolated. For example, what is the difference between GUI and command line in terms of display? It is the redundant display of data. We can display the same business data in different forms in different views, even on the PC side. Because the screen is large, we can allow the same data to be displayed in different forms at the same time. At this time, in order to coordinate as a whole, the update of this data will generate a lot of distribution and linkage relationships.

What is real time? In fact, this has multiple meanings. One of the more important factors is whether the server will actively push some business update information to the server. If it is used too much, there will be many distribution relationships.

When there are many distribution and linkage relationships, rxjs can show its advantages over generator and promise.

The above is the whole content of this article. I hope it will help you in your study, and I hope you can support developepaer more.

Recommended Today

Gson?So easy.

1. overview This article mainly describes the use of gson, including the basic types of serialization, object, array, collection, gson annotation, gson builder, formatting, custom serialization and deserialization In addition, the length of the article is long, and it is recommended to select the required parts to view. All examples are provided with complete source […]