Angular development has to mention another great artifact, RxJS. It is a very powerful tool that can help you better handle asynchronous programming in JavaScript. The following are some basic knowledge and resources about RxJS for your reference.
What is RxJS?
RxJS stands for Reactive Extended JavaScript. It is a library for processing event streams and asynchronous data streams, which can be combined to produce more complex results.
RxJS core concepts
- Observable: Represents a callable collection of future values or events.
- Observer: A collection of callback functions used to process the values emitted by the Observable, including next, error and complete.
- Subscription: Represents the execution of Observable and is mainly used to cancel the execution of Observable.
- Operators: Pure functions that allow pure transformation of values in Observable in a declarative manner, such as map(), filter(), concat(), etc.
- Subject: Equivalent to EventEmitter, and is the only way to multicast values to multiple observers.
Observable
In RxJS, an Observable represents a collection that can push 0 or more values, which is equivalent to a stream. There are several ways to create and convert Observables. Once an Observable is created, you can use a variety of additional operators to easily create, combine, and modify Observables.
Here are some core concepts about Observable:
Create Observable
You can use operators such as of
, from
, interval
, timer
, ajax
, create
etc. to create an Observable, depending on the type of data source you need to use. For example:
// 从一个静态值创建一个Observable
const myObservable = of('hello', 'world');
// 从数组中创建一个Observable
const myArrayObservable = from([1, 2, 3, 4, 5]);
// 创建一个每秒发送递增整数的Observable
const intervalObservable = interval(1000);
// 延迟三秒后发射单个值的Observable
const timerObservable = timer(3000, 1000);
Subscribe to Observable
Observables are lazy, they only start pushing values when you subscribe to them. You can use .subscribe()
methods to subscribe to an Observable and then process the emitted values.
myObservable.subscribe(value => console.log(value));
Unsubscribe
Once you subscribe to an Observable, you need to decide when to unsubscribe. Using .subscribe()
the returned subscription object, .unsubscribe()
the subscription can be unsubscribed by calling .
const subscription = myArrayObservable.subscribe(value => console.log(value));
// 在3秒后取消订阅
setTimeout(() => {
subscription.unsubscribe();
}, 3000);
handling errors
When an Observable emits error messages, you need to handle these errors accordingly. You can use catchError
the operator to catch and handle errors.
myObservable.pipe(
catchError(error => of(`An error occurred: ${
error}`))
).subscribe(value => console.log(value));
Connect multiple Observables
You can use connectable operators to subscribe to multiple Observables simultaneously and connect them together. Similar operators include merge
, concat
, combineLatest
, forkJoin
etc.
const observable1 = of('hello');
const observable2 = of('world');
const combinedObservable = combineLatest([observable1, observable2]);
combinedObservable.subscribe(value => console.log(value)); // ["hello", "world"]
Observer
In RxJS, an Observer represents an observer object that can handle zero or more values emitted by an Observable and can react to errors and completion events generated by the Observable.
The following are some core concepts about Observer:
Create Observer
You can create an Observer using three properties next
: , error
and .complete
const myObserver = {
next: value => console.log(value),
error: err => console.error(err),
complete: () => console.log('Observable completed')
};
Subscribe to Observable
In order to subscribe to an Observable, you need to pass the Observer as a parameter to the method on the Observable .subscribe()
.
myObservable.subscribe(myObserver);
When an Observable emits a new value, it calls the Observer's next
property to push the value. Likewise, when an Observable emits an error message, it calls the Observer's error
property and calls complete
the property to mark the Observable as completed.
const myObservable = of('hello', 'world');
// 订阅Observable
myObservable.subscribe({
next: value => console.log(value),
error: err => console.error(err),
complete: () => console.log('Observable completed')
});
handling errors
You may choose to define only Observers for next
and error
to handle error messages emitted by the Observable.
const myObserver = {
next: value => console.log(value),
error: err => console.error(err)
};
myObservable.subscribe(myObserver);
Handle completion event
You may choose to define only Observers for next
and complete
to handle events when the Observable completes.
const myObserver = {
next: value => console.log(value),
complete: () => console.log('Observable completed')
};
myObservable.subscribe(myObserver);
Subscription
Subscription is another important concept in RxJS. When we use Observable to subscribe to a data source, it returns a Subscription object, which is used to disconnect from the data source.
subscription
In order to subscribe to an Observable, we must call the Observable's subscribe() function. This function will return a Subscription object.
const subscription = myObservable.subscribe(value => {
console.log('Received value:', value);
});
unsubscribe
If we want to cancel a subscription, we can call the unsubscribe() method on the Subscription object.
subscription.unsubscribe();
After calling unsubscribe(), we will no longer receive any response from the Observable.
Resource cleanup after subscription
In addition, Subscription also provides the add() and remove() methods, both of which accept a sub-subscription (sub-Subscription).
They can be used to register and unregister subscriptions on the parent Subscription. When the parent Subscription is canceled, all registered child Subscriptions will automatically unsubscribe.
const parentSubscription = new Subscription();
const childSubscription = myObservable.subscribe(value => {
console.log('Received value:', value);
});
parentSubscription.add(childSubscription); // 注册子订阅
// 父 Subscription 将取消所有的子 Subscription。
parentSubscription.unsubscribe();
Operator
In RxJS, an Operator is a function that receives an Observable's input data and outputs a transformed Observable.
One or more Operators can be formed into a pipeline (pipe), through which the data is gradually passed and gradually converted into the form we need.
pipeline
A pipeline is a chain of calls composed of a series of Operators. In order to create a pipe, we need to use the pipe() method on the Observable and pass the Operator as a parameter.
import {
map, filter } from 'rxjs/operators';
const myObservable = of(1, 2, 3, 4, 5);
myObservable.pipe(
filter(value => value % 2 === 0), // 过滤偶数
map(value => value * 10) // 将值乘以 10
).subscribe(result => {
console.log('Received result:', result);
});
pure function
In RxJS, Operators should be pure functions - i.e. always produce the same output given a specific input. This makes them very easy to test, reuse and compose.
Common Operators
RxJS comes with many built-in Operators, such as map, filter, reduce, zip, merge, concat, etc. In addition, there are many community-developed Operator libraries, such as ngRx, ngrx-data, ngx-query and other libraries. These libraries provide a large number of Operators that can be used to simplify application development in different scenarios.
Subject
In RxJS, Subject is a special kind of Observable. The difference with normal Observables is that Subjects can emit values multiple times like events, and they send values simultaneously to all observers listening to the Subject.
There are two main variations of Subject:
BehaviorSubject
BehaviorSubject requires an initial value and will send the latest value to the observer whenever it is subscribed.
import {
BehaviorSubject } from 'rxjs';
const subject = new BehaviorSubject('Initial value');
subject.subscribe({
next: (v) => console.log(`ObserverA: ${
v}`)
});
subject.next('New value');
subject.subscribe({
next: (v) => console.log(`ObserverB: ${
v}`)
});
// output:
// ObserverA: Initial value
// ObserverA: New value
// ObserverB: New value
In the above example, we create a BehaviorSubject and pass it a value initially. When I send a new value to the BehaviorSubject, it sends this value to all the observers subscribed to it. Note that even new values sent after the second subscription will be sent to the new observer ObserverB.
ReplaySubject
ReplaySubject does not require initial values when created, but can cache the most recently emitted n values, and new subscribers will receive playback of these values immediately.
import {
ReplaySubject } from 'rxjs';
const subject = new ReplaySubject(2); // 缓存最近2个值
subject.subscribe({
next: (v) => console.log(`ObserverA: ${
v}`)
});
subject.next('Value 1');
subject.next('Value 2');
subject.next('Value 3');
subject.subscribe({
next: (v) => console.log(`ObserverB: ${
v}`)
});
// output:
// ObserverA: Value 1
// ObserverA: Value 2
// ObserverA: Value 3
// ObserverB: Value 2
// ObserverB: Value 3
In the above example, we created a ReplaySubject and passed parameter 2 in the constructor. This means that the principal will cache the most recent 2 values.
When I send 3 values to the playback topic, all observers subscribed to it receive these values.
Note that the new observer, ObserverB, receives the cached value immediately. In this example, 2 values are cached and 3 values have been emitted by the ReplaySubject, but the new observer will still be able to see the last two values (Value 2 and Value 3).
AsyncSubject
AsyncSubject only emits the last value upon completion (that is, when the Subject's complete() method is called).
import {
AsyncSubject } from 'rxjs';
const subject = new AsyncSubject();
subject.subscribe({
next: (v) => console.log(`ObserverA: ${
v}`)
});
subject.next('Value 1');
subject.next('Value 2');
subject.subscribe({
next: (v) => console.log(`ObserverB: ${
v}`)
});
subject.next('Value 3');
subject.complete();
// output:
// ObserverA: Value 3
// ObserverB: Value 3
In the above example, we created an AsyncSubject and subscribed to two observers. Then send three values and call the complete() method. After this, AsyncSubject caches the last value and completion notification internally and sends this value to all subscribed observers ObserverA and ObserverB.
Since AsyncSubject emits a value only when the complete() method is called, if the complete() method is called without emitting a value, there will be no emissions.
Subject is a very useful Observable variant that can handle various cases of synchronous and asynchronous code, as well as some use cases of more complex RxJS operators.
BehaviorSubject
BehaviorSubject is similar to the Subject above, but it emits the last value when subscribed and resends this value on each update.
import {
BehaviorSubject } from 'rxjs';
const subject = new BehaviorSubject('initial value');
subject.subscribe({
next: (v) => console.log(`ObserverA: ${
v}`)
});
subject.next('Value 1');
subject.next('Value 2');
subject.subscribe({
next: (v) => console.log(`ObserverB: ${
v}`)
});
subject.next('Value 3');
// output:
// ObserverA: initial value
// ObserverA: Value 1
// ObserverA: Value 2
// ObserverB: Value 2
// ObserverA: Value 3
// ObserverB: Value 3
In the above example, we created a BehaviorSubject and passed the initial value. We subscribe to two observers (ObserverA and ObserverB) and send three values. Note that when the second observer subscribes, it immediately receives the latest value, Value 2, and is updated each time a new value is sent.
There is an important note about BehaviorSubject: BehaviorSubject requires an initial value to be provided at creation time, since it may need to emit it when no other value is available.
BehaviorSubject is typically used to represent "state" or "data source", and multiple components can share the same BehaviorSubject instance to get and update that data. It can also be used to cache the latest values.
RxJS practical tutorial
Here are some good RxJS practical tutorials for your reference:
- RxJS official documentation
- RxJS Getting Started Tutorial
- Web development using RxJS
- RxJS: Operator Manual
- RxJS Beyond the Basics: Operators in Depth
- RxJS Master Class with Observable, Subject & Operators
Hopefully these resources will help you get started with RxJS quickly and make it easier to work with asynchronous streams in JavaScript programming.