How can I have only X observables active at a time? - rxjs

I'm using RxJS v6, but the answer could apply to RxJS v5 as well.
I'd like to make it so if I have say 8 values, I would only have 4 actively being processed at a time.
The code I have right now sends in 4 items, then waits till all 4 finish, then sends in the next 4. I'd like it to send in the first 4, then as an observable completes, another one comes in to be processed.
from([1, 2, 3, 4, 5, 6, 7, 8])
.pipe(
bufferCount(4),
concatMap(values => (
from(values)
.pipe(
mergeMap(value => (
timer(1000) // Async stuff would happen here
.pipe(
mapTo(value),
)
)),
)
)),
)
.subscribe(console.log)

You have to use the second parameter of mergeMap, which is a number that allows to specify the concurrency level you want.
So your code could look like
from([1, 2, 3, 4, 5, 6, 7, 8])
.pipe(
map(val => of(val)),
mergeMap(val => val, 4),
)
.subscribe(console.log)
or
of([1, 2, 3, 4, 5, 6, 7, 8])
.pipe(
mergeMap(val => val, 4),
)
.subscribe(console.log)
Consider that concatMap is mergeMap with concurrency level set to 1.
Read this SO post for more details on mergeMap.

Related

RxJs Zip only takes the first value from the sent stream

I have the following code using RxJs Zip operator:
of(1, 2, 3)
.pipe(zip(of(5), of(10)))
.subscribe((a) => console.log(a));
The output will be
[1, 5, 10]
But what happened to the 2 and 3 values initialized at of(1, 2, 3)?
The inner observables of(5) and of(10) only have one element. The static method zip() will only emit when all observable emit a corresponding notification.
For eg.
of(1, 2, 3)
.pipe(zip(of(5, 6, 7), of(10, 11, 12)))
.subscribe((a) => console.log(a));
will emit
[1, 5, 10]
[2, 6, 11]
[3, 7, 12]
I must also stress here that the static method zip() is deprecated and has since been replaced by the zip() function. Using the new zip() the equivalent for the above would be
import { of, zip } from 'rxjs';
zip(
of(1, 2, 3),
of(5, 6, 7),
of(10, 11, 12)
).subscribe((a) => console.log(a));

Rxjs operator delay doesn't delay the emitted values from of

I would like to emit a value every second. So I created the following code:
of(1, 2, 3, 4, 5, 6, 7, 8)
.pipe(map(val => val + 10), delay(1000))
.subscribe(console.log);
DEMO
But this code still prints all the values at once with an initial delay of 1 second. But how can you delay every value?
You can achieve that by using RxJS interval function like the following:
interval(1000)
.pipe(map((val) => val + 10))
.subscribe(console.log);

rxjs `toArray` is not working as expected

I tried to de-duplicate the sent arrays and then merge them into arrays
import { from, BehaviorSubject, distinct, switchMap, toArray } from "rxjs";
let list$ = new BehaviorSubject([1, 2, 3, 2, 3, 5]);
list$.pipe(
switchMap((e) => from(e)),
distinct(),
toArray()
).subscribe(console.log);
expected result:
BehaviorSubject -> [1, 2, 3, 2, 3, 5]
switchMap -> 1 2 3 2 3 5
distinct -> 1 2 3 5
toArray -> [1, 2, 3, 5]
console.log -> [1, 2, 3, 5]
Actually did not receive any value in console.log, why is this and how can I work as expected
"rxjs": "^7.2.0"
toArray only emits once the source observable completes.
The following should work as expected.
list$.pipe(
take(1),
switchMap(e => e),
distinct(),
toArray()
).subscribe(console.log);
If what you really want to do is filter unique values of an array, then RxJS's unique operator might be overkill. I wouldn't bother turning your array into a stream. Just filter the array.
list$.pipe(
map(a => [...new Set(a)])
).subscribe(console.log);
So, if the source does not stop after the first notification, I assume that it will continue emit other arrays and that you want to filter the duplicates on each array emitted. In other words, if the list$ of your example emits first [1, 2, 3, 2, 3, 5] and then [3, 2, 1, 6, 6, 6,] what you want to log are 2 arrays, [1, 2, 3, 5] and [3, 2, 1, 6].
If my assumption is right, than the solution could be the following
list$.pipe(
concatMap((e) => from(e).pipe(
distinct(),
toArray()
)),
).subscribe(console.log);
The trick here is that each from(e) stream will complete when there are no more elements in the array. Therefore, since it completes, the toArray operator can actually work.
scan could do the trick.
list$.pipe(
switchMap((e) => from(e)),
distinct(),
scan((acc, curr) => [...acc, curr], []),
).subscribe(console.log);
// will print: [1], [1, 2], [1, 2, 3], [1, 2, 3, 5]
You could insert debounceTime in the pipe, if you need less emissions:
list$.pipe(
switchMap((e) => from(e)),
distinct(),
scan((acc, curr) => [...acc, curr], []),
debounceTime(0)
).subscribe(console.log); // will print [1, 2, 3, 5]
If the only requirement is to remove duplicates, you're better off handling it using vaniall JS. See here: https://stackoverflow.com/a/9229821/6513921
We'll take the shortest solution without any regards to performance: uniq = [...new Set(array)];
You could then write a custom RxJS operator to include it in the pipe with other operators.
const { BehaviorSubject, from } = rxjs;
const { map, switchMap } = rxjs.operators;
const uniqueArray = (obs$) => {
return (obs$) => {
return obs$.pipe(
map(arr => [...new Set(arr)])
);
};
};
const sub = new BehaviorSubject([1, 2, 3, 2, 3, 5]);
sub.asObservable().pipe(
uniqueArray()
).subscribe(console.log);
sub.next([6, 3, 1, 6, 7, 1, 1]);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://unpkg.com/rxjs#6.2.2/bundles/rxjs.umd.min.js"></script>

Any way to reset Interval value if a certain condition met?

Suppose I have an array of integers and I want to iterate over this array in a interval of one second, the current second being the index of the array. Once the index reaches the end, I want to reset the interval value. Whats the right approach to achieve this behavior?
Stackblitz example
Code:
const array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
interval(1000)
.pipe(
tap(second => {
if (array[second]) {
console.log(array[second]);
} else {
//reset interval to run in a cyclic manner?
}
})
).subscribe();
So you want to repeat the same sequence indefinatelly.
import { from, of } from "rxjs";
import { concatMap, repeat, delay } from "rxjs/operators";
const array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
from(array)
.pipe(
concatMap(second => of(second).pipe(
delay(second * 1000),
)),
repeat(),
)
.subscribe(console.log);
Live demo: https://stackblitz.com/edit/rxjs-interval-pmlj6e

Why does switchMap operator only emit the last value when used with a promise?

I have some trouble to understand this. When I use the switchMap operator with an Observable it emits all the values as expected:
Observable.from([1, 2, 3, 4, 5])
.do(console.log)
.switchMap(i => Observable.of('*' + i))
.do(console.log)
.subscribe();
Results:
1
*1
2
*2
3
*3
4
*4
5
*5
But when I replace the the Observable by a Promise I get a different behaviour:
Observable.from([1, 2, 3, 4, 5])
.do(console.log)
.switchMap(i => new Promise((resolve) => resolve('*' + i)))
.do(console.log)
.subscribe();
Results:
1
2
3
4
5
*5
This works as expected. The unexpected behaviors as you said is because Observable.from and Observable.of are always strictly synchronous while new Promise necessarily doesn't (I'm not sure about the specification so maybe that's what Promise has to do in all browsers).
Anyway you can force Observable.from emit asynchronously by passing the async Scheduler.
import { Observable } from 'rxjs';
import { async } from 'rxjs/scheduler/async';
Observable.from([1, 2, 3, 4, 5], async)
.do(console.log)
.switchMap(i => new Promise((resolve) => resolve('*' + i)))
.do(console.log)
.subscribe();
Now every emission is in a new frame just like Promise and the output is as you expected.
See live demo (open console): https://stackblitz.com/edit/rxjs5-gfeqsn?file=index.ts
Try mergeMap, maybe it is what you are looking for.

Resources