How to skip/consume/swallow a value after tapped into it? - rxjs

I am trying to achieve something like below
from([1,2,3,4]).pipe(
Filter(v=> v % 2 === 0),
Tap( () => call server for even number only),
Swallow it, don't emit
).subscribe(()=>{log my odd numbers})
I know I can use filter( () => false) but was wondering if there is a better solution to it? Thanks

What do you mean? Swallow what? Based on what? Filter accepts/rejects values based on a predicate, if you want a better solution, what criteria would you prefer?
If you want to swallow everything, that's not functionally much different from terminating your Observable:
of([1,2,3,4]).pipe(
filter(v=> v % 2 === 0)
).subscribe(() => call server here);
Though I would expect an error as you can't use % on arrays. Though this should work:
from([1,2,3,4]).pipe(
filter(v=> v % 2 === 0)
).subscribe(() => call server here);
or
of([1,2,3,4]).pipe(
filter(v=> v.length % 2 === 0)
).subscribe(() => call server here);
Higher-order operators
If your server call is an observable-like object (promise, array, observable, ect), then you can use the higher-order operators like mergeMap, switchMap, concatMap, ect. In this case, you're not 'swallowing' a value so much as transforming (mapping) it into your call.
This also means whatever value your call server observable emits still gets passed forward. This really is a stream transformation rather than filtering/swallowing values.
from([1,2,3,4]).pipe(
filter(v=> v % 2 === 0),
concatMap(v => call server observable)
);
Update
Helper Function
/****
* Pipeable Operator:
* Takes arrays emitted by the source and spaces out their
* values by the given interval time in milliseconds
****/
function intervalArray<T>(intervalTime = 1000): OperatorFunction<T[], T> {
return pipe(
concatMap((v: T[]) =>
concat(
...v.map((value: T) =>
EMPTY.pipe(
delay(intervalTime),
startWith(value)
)
)
)
)
);
}
Mocking a hot observerable emitting values overtime:
const source = of([1,2,3,4,5]).pipe(
intervalArray(250),
shareReplay(1)
);
Solution
Here is how I might implement your question for such an observable. The simplest thing to do is to just subscribe to your observable twice:
source.pipe(
filter(v => v % 2 === 0),
concatMap(evenNumber => this.serviceCall(evenNumber))
).subscribe(/* serviceCallResult => Do Nothing */);
source.pipe(
filter(v => v % 2 !== 0)
).subscribe(oddNumber => console.log(
"I got an odd Number: ",
oddNumber
));

Related

RXJS pipe chaining vs single pipe

I was wondering is there any difference between using a single myObservable.pipe(...) vs chaining myObservable.pipe(...).pipe(...).
Question is are those 2 examples identical? Is there a use case for pipe chaining?
Example with single pipe:
from([1, 2, 3])
.pipe(
delay(1000),
map((value: number) => value * 2),
map((value: number) => value * 3)
)
.subscribe((value) => {
console.log('result:', value);
});
Example with chaining pipes:
from([1, 2, 3])
.pipe(delay(1000))
.pipe(map((value: number) => value * 2))
.pipe(map((value: number) => value * 3))
.subscribe((value) => {
console.log('result:', value);
});
No, there isn't. However, I would like to help you understand why:
This code:
from([1,2,3]).pipe(
delay(1000)
)
is 100% equivalent to:
delay(1000)(
from([1, 2, 3])
)
And this code:
from([1, 2, 3])
.pipe(
delay(1000),
map((value: number) => value * 2),
)
is the same as:
map((value: number) => value * 2)(
delay(1000)(
from([1, 2, 3])
)
)
Etc, etc.
The thing is that piepable operators (the ones that you can use inside pipe) are "Observable enhancers" (a type of higher order functions). What those functions return is a function that will enhance the Observable that has received as an argument and it will return a new Observable with some "enahnced" behavior.
Therefore, pipe is just sugar for composing those Observable enhancers in a more declarative manner.
For instance, a basic implementation of the map piepable operator would look like this:
const map = <I, O>(mapper: (input: I) => O) =>
(source$: Observable<I>): Observable<O> =>
new Observable<O>(observer => source$.subscribe({
next: (value) => {
observer.next(mapper(value));
},
error: (e) => {
observer.error(e);
},
complete: () => {
observer.complete();
}
}))
RxJS is known for its operators. Essentially there is a kind of operators that take an observable as input and return another observabl, these are pipeable operators for example filter, map etc.
A Pipeable Operator is a function that takes an Observable as its input and returns another Observable. It is a pure operation: the previous Observable stays unmodified.
Now, the pipe operator just gives us syntax flexibility, to combine operators (logically thinking they are inside a pipe, where one would be run after the other).
Theoretically it doesn't make a difference that you chain pipe operators, but the sole purpose of pipe operator is to combine multiple operators, so its a good practice to combine group of operators inside a single pipe.

How do I combine 2 boolean observables into a single boolean observable if either are true?

To make my specific condition as easy as possible I have the following observables.
const a = of(true);
const b = of(true);
I am looking to determine that if either or both of these are true then return an observable of true but if both are false, return an observable of false.
if (a || b) ? true : false;
I seem to be tripping over how to combine them correctly, I was using combineLatest to get an array.
combineLatest([
a,
b,
])
// [true, true]
I think that https://xgrommx.github.io/rx-book/content/observable/observable_instance_methods/some.html is what I need but that seems long gone and searching for "some" isn't amazing.
combineLatest([
a,
b,
]).pipe(
some(x => x === true)
)
You can use combineLatest to produce single observable based on multiple source observables:
const myBool$ = combineLatest([a$, b$]).pipe(
map(([a, b]) => a || b)
);
Your combined observable will emit an array of values whenever any of the sources emit (note it won't emit for the first time until each source emits at least once). You can then use the map operator to transform the emitted array into your single boolean.
The code above will emit the boolean value whenever any source emits. However, it may not be desirable to receive emissions if the resultant value hasn't changes (ex: if a and b are both true, then b changes to false, the result is still true).
We can prevent emitting the same result by using distinctUntilChanged:
const myBool$ = combineLatest([a$, b$]).pipe(
map(([a, b]) => a || b),
distinctUntilChanged()
);

Lazily delay each value of a infinite stream

As the title suggests, I want to delay each value in an iterable by some amount of time while keeping the iterable lazily evaluated. Here's the closest I've got so far which works fine for finite iterables or those which don't throw an error
function* iter () {
let i = 0
while (true) yield i++
}
rxjs.zip(
rxjs.from(iter()),
rxjs.timer(500, 500),
x => x
).subscribe(console.log)
Another way might be:
const source$ = interval(0);
source$.pipe(
concatMap(x =>
of(x).pipe(delay(500))
)
).subscribe(console.log);

RxJS throttle same value but let new values through

"Here you have", someone says and you are given this input stream of values that you somewhat want to do distinctUntilChanged() upon...
Input: '1-1----11---2--1122----1---2---2-2-1-2---|'
Output: '1-----------2--1-2-----1---2-------1-2---|'
Nothing weird so far,
But now someone says "it's okey" if the same value comes again, "but only if it's not to soon!". I want at least '----' ticks between the same value. "Okey" you say and you add a throttle
const source = new Subject<number>();
// mysterious cave troll is randomly source.next(oneOrTwo)
const example = source.pipe(throttle(val => interval(4000)));
Input: '1-1----11---2--1122----1---2---2-2-1-2-----|'
Output: '1------1----2----2-----1-------2-----2-----|'
"That's not what I want! Look at all the value you missed", referring to that you throttle in regards to all values being streamed.
Input: '1-1----11---2--1122----1---2---2-2-1-2-----|'
Output: '1------1----2----2-----1-------2-----2-----|'
'-------------->1<--------->2<----->1<------|' <-- Missed values
"Here, let me show show you" the mysterious man says and gives you this
Wanted output
Input: '1-1----11---2--1112----1---2---2-2-1-2-----|'
Output: '1------1----2--1--2----1---2-----2-1-------|'
My answer to this is that it feels like a combined window wouldn't do.
From someone more experienced,
is this a hard problem to solve? (or have I missed an obvious solution)
First I came up with idea to somehow combine distinctUntilChanged() and throttleTimte(), however it was not possible for me to come up with solution and then I tried something else.
The operator I came up with is throttleDistinct() that works as you would like to: StackBlit Editor Link
It has 2 parameters which are:
duration: number which is in milliseconds and is similar to
duration in throttleTime(duration: number)
equals: (a: T, b: T) => boolean which is function to compare if previous item is equal to next item, which has default
implementation of (a, b) => a === b
import { of, fromEvent, interval, Observable } from 'rxjs';
import { map, scan, filter, } from 'rxjs/operators';
const source = fromEvent(document, 'keypress')
.pipe(map((x: any) => x.keyCode as number))
source
.pipe(
throttleDistinct(1000),
)
.subscribe((x) => console.log('__subscribe__', x));
export function throttleDistinct<T>(
duration: number,
equals: (a: T, b: T) => boolean = (a, b) => a === b
) {
return (source: Observable<T>) => {
return source
.pipe(
map((x) => {
const obj = { val: x, time: Date.now(), keep: true };
return obj;
}),
scan((acc, cur) => {
const diff = cur.time - acc.time;
const isSame = equals(acc.val, cur.val)
return diff > duration || (diff < duration && !isSame)
? { ...cur, keep: true }
: { ...acc, keep: false };
}),
filter((x) => x.keep),
map((x) => x.val),
)
}
}
Off the top of my head, you want to buffer by the time interval, then distinct within each buffer.
Effectively you want to restart / reboot the distinct run every n milliseconds.
source.pipe(
bufferTime(ms),
mergeMap(bufferArray => from(bufferArray).pipe(distinctUntilChanged()) )
)
This is my second attempt, it filters the stream by output (rather than taking distinctUntil) then throttles and merges the two streams.
Of course, we may not have a known set of values (1,2,...n).
If I can figure out that wrinkle, will add a further example.
const output = merge(
source.pipe( filter(x => x === 1), throttle(val => interval(ms))),
source.pipe( filter(x => x === 2), throttle(val => interval(ms)))
)
Here is my check (ms = 4000)
input 1-1----11---2--1112----1---2---2-2-1-2-----
expected 1------1----2--1--2----1---2-----2-1-------
filter(1) 1-1----11------111-----1-----------1-------
throttle(1) 1------1-------1-------1-----------1-------
filter(2) ------------2-----2--------2---2-2---2-----
throttle(2) ------------2-----2--------2-----2---------
merged 1------1----2--1--2----1---2-----2-1-------
expected 1------1----2--1--2----1---2-----2-1-------
Extending to n values
I think this will work where the set of values in the stream is not known in advance (or has a large range so extending the previous answer is impractical).
It should work as long as the source completes.
merge(
source.pipe(
distinct().pipe(
mapTo(distinctVal => source.pipe(
filter(val = val === distinctVal),
throttle(val => interval(ms))
)
)
)
)
I don't have a proof yet, will post that next.
Here is a tricky solution base on theory of operators, but I can't sure it really works, because I will need to mock a source emission first.
So throttle and distinct stream always have the latest value cached, zip make sure they always got emitted in pair, zip will always emit when any of the stream emit because it's shareReplay(1).
We always take the value emit from distinctStream, even when zip stream is trigger by throttle, because distinctStream always have the last cached value.
const throttleStream= source.pipe(throttle(val => interval(4000)),shareReplay(1))
const distinctStream= source.pipe(distinctUntilChanged(),shareReplay(1))
zip(throttleStream,distinctStream).pipe(
map((t,d)=>d)
)
I found a solution that works, does someone have any take on this?
source.pipe(
windowTime(4000),
concatMap(obs => obs.pipe(distinct()))
);
Examples from before, in a StackBlitz example
UPDATE: this does not actually work 100%. It only take the current window into consideration. So you can for example have
`[1-12][2---]` which would give `1--22---|`
where [----] would represent the time window. In other words, if a value is first emitted last in one window and emitted first in the next window, the same value will pass through right after each other.
Thanks #eric99 for making me realize this.

rxjs 5 publishReplay refCount

I can't figure out how publishReplay().refCount() works.
For example (https://jsfiddle.net/7o3a45L1/):
var source = Rx.Observable.create(observer => {
console.log("call");
// expensive http request
observer.next(5);
}).publishReplay().refCount();
subscription1 = source.subscribe({next: (v) => console.log('observerA: ' + v)});
subscription1.unsubscribe();
console.log("");
subscription2 = source.subscribe({next: (v) => console.log('observerB: ' + v)});
subscription2.unsubscribe();
console.log("");
subscription3 = source.subscribe({next: (v) => console.log('observerC: ' + v)});
subscription3.unsubscribe();
console.log("");
subscription4 = source.subscribe({next: (v) => console.log('observerD: ' + v)});
subscription4.unsubscribe();
gives the following result:
call observerA: 5
observerB: 5 call observerB: 5
observerC: 5 observerC: 5 call observerC: 5
observerD: 5 observerD: 5 observerD: 5 call observerD: 5
1) Why are observerB, C and D called multiple times?
2) Why "call" is printed on each line and not in the beginning of the line?
Also, if i call publishReplay(1).refCount(), it calls observerB, C and D 2 times each.
What i expect is that every new observer receives the value 5 exactly once and "call" is printed only once.
publishReplay(x).refCount() combined does the following:
It create a ReplaySubject which replay up to x emissions. If x is not defined then it replays the complete stream.
It makes this ReplaySubject multicast compatible using a refCount() operator. This results in concurrent subscriptions receiving the same emissions.
Your example contains a few issues clouding how it all works together. See the following revised snippet:
var state = 5
var realSource = Rx.Observable.create(observer => {
console.log("creating expensive HTTP-based emission");
observer.next(state++);
// observer.complete();
return () => {
console.log('unsubscribing from source')
}
});
var source = Rx.Observable.of('')
.do(() => console.log('stream subscribed'))
.ignoreElements()
.concat(realSource)
.do(null, null, () => console.log('stream completed'))
.publishReplay()
.refCount()
;
subscription1 = source.subscribe({next: (v) => console.log('observerA: ' + v)});
subscription1.unsubscribe();
subscription2 = source.subscribe(v => console.log('observerB: ' + v));
subscription2.unsubscribe();
subscription3 = source.subscribe(v => console.log('observerC: ' + v));
subscription3.unsubscribe();
subscription4 = source.subscribe(v => console.log('observerD: ' + v));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.1.0/Rx.js"></script>
When running this snippet we can see clearly that it is not emitting duplicate values for Observer D, it is in fact creating new emissions for every subscription. How come?
Every subscription is unsubscribed before the next subscription takes place. This effectively makes the refCount decrease back to zero, no multicasting is being done.
The issue resides in the fact that the realSource stream does not complete. Because we are not multicasting the next subscriber gets a fresh instance of realSource through the ReplaySubject and the new emissions are prepended with the previous already emitted emissions.
So to fix your stream from invoking the expensive HTTP request multiple times you have to complete the stream so the publishReplay knows it does not need to re-subscribe.
Generally: The refCount means, that the stream is hot/shared as long as there is at least 1 subscriber - however, it is being reset/cold when there are no subscribers.
This means if you want to be absolutely sure that nothing is executed more than once, you should not use refCount() but simply connect the stream to set it hot.
As an additional note: If you add an observer.complete() after the observer.next(5); you will also get the result you expected.
Sidenote: Do you really need to create your own custom Obervable here? In 95% of the cases the existing operators are sufficient for the given usecase.
This happens because you're using publishReplay(). It internally creates an instance of ReplaySubject that stores all values that go through.
Since you're using Observable.create where you emit a single value then every time you call source.subscribe(...) you append one value to the buffer in ReplaySubject.
You're not getting call printed at the beginning of each line because it's the ReplaySubject who emits its buffer first when you subscribe and then it subscribes itself to its source:
For implementation details see:
https://github.com/ReactiveX/rxjs/blob/master/src/operator/multicast.ts#L63
https://github.com/ReactiveX/rxjs/blob/master/src/ReplaySubject.ts#L54
The same applies when using publishReplay(1). First it emits the buffered item from ReplaySubject and then yet another item from observer.next(5);

Resources