Reputation: 9499
The docs define Observable.lift(operator: Operator)
as:
Creates a new Observable, with this Observable as the source, and the passed operator defined as the new observable's operator.
and Observable.pipe(operations: ...*)
as:
Used to stitch together functional operators into a chain. Returns the Observable result of all of the operators having been called in the order they were passed in.
So clearly .pipe
can accept multiple operators, which .lift
cannot. But pipe
can also accept a single operator, so this cannot be the only difference. From the docs alone it isn't clear to me what they are both for and why they exist. Can someone please explain the purpose of each of these functions, and when each of them should be used?
The following code (typescript):
let myObservable = Observable.of(1, 2, 3);
let timesByTwoPiped = myObservable.pipe(map(n => n * 2));
let timesByTwoLift = myObservable.lift(new TimesByTwoOperator());
timesByTwoPiped.subscribe(a => console.log('pipe:' + a));
timesByTwoLift.subscribe(a => console.log('lift:' + a));
and TimesByTwoOperator
:
class TimesByTwoOperator implements Operator<number, number> {
call(subscriber: Subscriber<number>, source: Observable<number>): void | Function | AnonymousSubscription {
source.subscribe(n => {
subscriber.next(n * 2);
});
}
}
Seems to achieve the same result using both .lift
and .pipe
. This experiment shows I'm correct in thinking that both lift and pipe can be used to achieve the same thing, albeit with the pipe version being more succinct in this case.
As the Operator
type that is passed in to .lift
is given full access to the source observable and subscriptions, clearly powerful things could be achieved with it; for example keeping state. But I'm aware that the same sort of power can also be achieved with .pipe
, for example with the buffer
operator.
It's still not clear to me why they both exist and what each is designed for.
Upvotes: 22
Views: 18610
Reputation: 11
ADT(algebraic data types - alternative to OOP classes)
Lifting is often used together with Monads. RXJS library itself - represents Monad(and bunch of others) ADT.
"lift" comes from FP and ADT. Here is the descriptive explanation what term "lift" means: https://wiki.haskell.org/Lifting
TLDR:
const sum = a => b => a + b;
console.log(sum(2)(3)); // 5
// and now we need to reuse our "sum" within a Monad level.
// it means we need to "lift" our "sum" function to the Monad level:
// assumption: our Monad - is just an Array
const monad0 = [2];
const monad1 = [3];
// lift1 === fmap
// lift1:: Monad M: (a -> b) -> M a -> M b
const lift1 = f => Ma => [f(Ma[0])];
// lift2:: Monad M: (a -> b -> c) -> M a -> M b -> M c
const lift2 = f => Ma => Mb => [f(Ma[0])(Mb[0])];
// lift3 etc...
console.log(lift2(sum)(monad0)(monad1)); // [5]
Upvotes: 1
Reputation: 54771
Can someone please explain the purpose of each of these functions, and when each of them should be used?
lift()
creates a new observable object but pipe()
does not. pipe()
follows the functional programming paradigm and lift()
is object-oriented.
They both accept functions as input arguments, but the advantage of pipe()
is that there is no extra observable created.
When you use lift()
a single operator is attached to a new observable, and when this new observable is subscribed the attached operator intercepts the stream before it is subscribed too.
This is different from how pipe()
works because an operator returns the same observable will yield no changes to the original observable.
pipe()
was introduced after lift()
and I think this is the preferred way to chain operators.
Upvotes: 8
Reputation: 305
I've found a nice in-depth discussion on this subject and the potential idea of removing Observable.lift in favor of Observable.pipe here: https://github.com/ReactiveX/rxjs/issues/2911
TL;DR
Now let's compare the "pure" lift and pipe signatures:
// I'm intentionally ignoring pipe's multiple operator function args, // since we could redefine lift to also take multiple operator functions type Operator = <T, R>(sink: Observer<R>) => Observer<T> type lift = <T, R>(src: Observable<T>, op: Operator) => Observable<R>; type pipe = <T, R>(op: Operator) => (src: Observable<T>) => Observable<R>
- pipe's operator function maps an Observable to an Observable
- lift's operator function maps an Observer to an Observer
This is just another way to represent the idea of either:
- building an Observable chain down from the source to the sink
- or building an Observer chain up from the sink to the source
Upvotes: 10