Use operators and avoid nesting subscriptions in RxJS, RxSwift, or RxJava.

Use operators and avoid nesting subscriptions in RxJS, RxSwift, or RxJava

Nested subscription is an anti-pattern that is commonly found in implementations with Rx code.
You can easily fall for this anti-pattern when the amount of code increases when an adjustment or modification is required.


Let's look at the typical example of nested subscriptions:

network.getToken(apiKey)
.subscribe(token => {
if (token.isValid) {
cache.storeToken(token)
.subscribe(saved =>
console.log('Token stored: ', saved));
}
});

Why is it not recommended practice?

  • It is a misuse of the components.
  • Memory leaks are induced.
  • Unexpected behaviors are induced.
The Rx subscription has been designed to link an Observer to an Observable (an item-emitting source).
Nesting a subscription produces the Observable's binding to another Observable; that is, they try to use an Observable as an Observer. That is an inappropriate use of such components in Rx.

Only Subjects are designed with that capability and yet should be used only in exceptional cases.


Subscription should be used to bind an Observer to an Observable, not to attempt to orchestrate operations, the task of orchestrating processes is done through operators.

A subscription is created every time observer code is linked to a source. Because the subscription consumes memory resources, it must be released once it is no longer needed. Nesting subscriptions make subscriptions challenging to detect and control, which can lead to memory leaks.

When two tasks are linked through nested subscriptions, there are no guarantees in the order of execution of one task concerning another, that is, a correct orchestration of the tasks is not guaranteed. For this purpose, there are operators such as flatMap, concatMap, switchMap, etc.

How to correct nested subscriptions?

That is accomplished through the application of orchestration operators. The following table shows some of the most used ones.
The above code could be corrected like so:

En RxJS

network.getToken(apikey)
.pipe(
filter(token => token.isValid),
concatMap(token => this.cache.storeToken(token))
)
.subscribe(saved =>
console.log('Token stored: ', saved));

En RxSwift

network.getToken(apikey)

    .filter { token in token.isValid }

    .concatMap { token in self.cache.storeToken(token) }

    .subscribe(onNext: { saved in

        print("Token stored: \(saved)")

    })

    .disposed(by: disposeBag)


En RxJava

network.getToken(apiKey)
.filter(Token::isValid)
.concatMap(token -> this.cache.storeToken(token))
.subscribe(saved ->
Log.d(TAG, "Token stored: " + saved));


Hidden Nested Subscriptions

Watch out for those scenarios where nested subscription is not so obvious. Some cases are shown below.

Case 1: Although the code seems organized, the subscription is being nested using the storeToken method.

getToken(apikey: string) {
this.network.getToken(apikey)
.subscribe(token => {
this.storeToken(token);
});
}

storeToken(token: Token) {
if (token.isValid) {
this.cache.storeToken(token)
.subscribe(saved =>
console.log('Token stored: ', saved));
}
}

Case 2: In this case, the Subject is used to transmit the message, but it is being overused. The same result could be obtained only with an Observable.

const oneSubject = new Subject();
const twoSubject = new Subject();

oneSubject.subscribe(message => console.log(message));

twoSubject.subscribe(message => {
oneSubject.next(message);
});

twoSubject.next("A");
twoSubject.next("B");
twoSubject.next("C");

Applying a thorough code review or linters in the tools could serve as a filter to avoid this anti-pattern.


If you want to find more information about RxJS, RxSwift or RxJava, I recommend you check the guide book:

The Clean Way to Use Rx. Also the Spanish version.

Happy reading.



Previous
Next Post »
Thanks for your comment