X Tutup
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions Part 3 - Taming the sequence/7. Custom operators.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,17 +183,17 @@ Internally, every Rx operator does 3 things
2. It transforms the observed sequence according to the operator's purpose.
3. It pushes the modified sequence to its own subscribers, by calling `onNext`, `onError` and `onCompleted`.

The `compose` operator works with a method that makes an observable out of another. By doing this, it spares you the trouble of doing the 3 steps above manually. That presumes that you can do the transformation by using existing operators. If the operators don't already exist, or if you think you can get better performance manually, you need to receive items manually, process them and re-push them. An `Observable.Transformer` that does this would include a `subscribe` to the source `Observable` and the creation of a new `Observable` to be returned, possibly a `Subject`.
The `compose` operator works with a method that makes an observable out of another. In doing so, it spares you the trouble of doing the 3 steps above manually: the intermediate subscribing and pushing is implicit within an Rx chain. That presumes that you can do the transformation by using existing operators. If the operators don't already exist, you need to do the processing in the traditional Java OOP way. This means extracting the values from the pipeline and re-pushing when processed. An `Observable.Transformer` that does this would include an explicit subscription to the source `Observable` and/or the explicit creation of a new `Observable` to be returned.

There's a simpler way with `lift`. The `lift` operator is similar to `compose`. Instead of transforming `Observable`, it transforms `Subscriber`.
You'll find that this is often just boilerplate, and that you can avoid some of it by going to a lower level. The `lift` operator is similar to `compose`, with the difference of transforming a `Subscriber`, instead of an `Observable`.

```java
public final <R> Observable<R> lift(Observable.Operator<? extends R,? super T> lift)
```

And `Observable.Operator<R,T>` is an alias for `Func1<Subscriber<? super R>,Subscriber<? super T>>`: a function that will transform a `Subscriber<R>` into `Subscriber<T>`. By dealing directly with `Subscriber` we avoid involving `Observable`.
And `Observable.Operator<R,T>` is an alias for `Func1<Subscriber<? super R>,Subscriber<? super T>>`: a function that will transform a `Subscriber<R>` into `Subscriber<T>`. By dealing directly with `Subscriber` we avoid involving `Observable`. The boilerplate of subscribing to and creating `Observable` types will be handled by lift.

This seems backwards at first: to turn an `Observable<T>` into `Observable<R>`, we need a function that turns `Subscriber<R>` into `Subscriber<T>`. To understand why that is the case, remember that a subscription begins at the end of the chain and is propagated to the source. In other words, a subscirption goes backwards through the chain of operators. Each operator receives a subscription (i.e. is subscribed to) and uses that subscription to create a subscription to the preceeding operator.
If you studied the signature, there's something which seems backwards at first: to turn an `Observable<T>` into `Observable<R>`, we need a function that turns `Subscriber<R>` into `Subscriber<T>`. To understand why that is the case, remember that a subscription begins at the end of the chain and is propagated to the source. In other words, a subscription goes backwards through the chain of operators. Each operator receives a subscription (i.e. is subscribed to) and uses that subscription to create a subscription to the preceeding operator.

In the next example, we will reimplement `map`, without using the existing implementation or any other existing operator.

Expand Down Expand Up @@ -299,7 +299,7 @@ Completed
Unsubscribed
```

Despite what our observable tried to emit, the end result obeyed the Rx contract. That happened because `subscribe` will temrinate the subscription when the sequence terminates (or was supposed to have terminated). This doesn't mean that the problem will always be taken care for us. There is also a method called `unsafeSubscribe`, which won't unsubscribe automatically.
Despite what our observable tried to emit, the end result obeyed the Rx contract. That happened because `subscribe` terminated the subscription when it (very reasonably) thought that the sequence ended. This doesn't mean that the problem will always be taken care for us. There is also a method called `unsafeSubscribe`, which won't unsubscribe automatically.

```java
Observable<Integer> source = Observable.create(o -> {
Expand Down Expand Up @@ -337,7 +337,7 @@ Completed
Completed
```

Our subscriber's intended behaviour was identical to the previous example (we created an instance of `Subscriber` because `unsafeSubscribe` doesn't have overloads that take lambdas). However, we can see here we weren't unsubscribed and we kept receiving notifications.
Our subscriber's intended behaviour was identical to the previous example (we created an instance of `Subscriber` because `unsafeSubscribe` doesn't have overloads that take lambdas). However, we can see here we weren't unsubscribed and we kept receiving notifications.

`unsafeSubscribe` is unsafe in other regards as well, such as error handling. It's usefulness is limited. The documentation says that it should only be used for custom operators that use nested subscriptions. To protect such operators from receiving and illegal sequence, we can apply the `serialize` operator

Expand Down Expand Up @@ -388,6 +388,16 @@ We here that, despite the fact that we did not unsubscribe, the illegal notifica
If the ability to use your custom operator in the chain like a standard operator is not convincing enough, using `lift` has one more unexpected advantage. Standard operators are also implemented using `lift`, which makes `lift` a hot method at runtime. JVM optimises for `lift` and operators that use `lift` receive a performance boost. That can include your operator, if you use `lift`.


## Choosing between `lift` and `compose`

Both `lift` and `compose` are meta-operators, used for injecting a custom operator into the chain. In both cases, the custom operator can be implemented as a function or a class.
* `compose`: `Observable.Transformer` or `Func<Observable<TSource>, Observable<TReturn>>`
* `lift`: `Observable.Operator` or `Func<Subscriber<TReturn>, Subscriber<TSource>>`

Theoretically, any operator can be implemented as both `Observable.Operator` and `Observable.Transformer`. The choice between the two is a question of convenience, and what kind of boilerplate you want to avoid.

* If the custom operator is a composite of existing operators, `compose` is a natural fit.
* If the custom operator needs to extract values from the pipeline to process them and then push them back, `lift` is a better fit.


#### Continue reading
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,13 @@ This guide aims to introduce a beginner reactive programmer to the complete powe
No experience with either reactive or functional programming is needed to follow the book. Familiarity with the basics of Java is required.

[Begin learning](/Part 1 - Getting Started/1. Why Rx.md)

### Structure

The content of this book is meant to be read from start to finish. It begins with the basics and every subsequent chapter introduces increasingly advanced features and concepts. Sections of the book are intended to be self-containing and to-the-point, so that the book can be referred back to by non-beginners.

The examples used in the book are also [available in complilable](/tests/java/itrx) java files in two formats:
* Examples that print to standard output (recommended for first-time readers)
* Silent, self-checking examples in the form of [JUnit](http://junit.org/) tests.
The readers are invited to study whichever style suits them best.

X Tutup