Observable.prototype.concatAll does not seem to yield expected result - rxjs5

With this code in mind:
const Rx = require('rxjs');
var i = 3;
const obs = Rx.Observable.interval(10)
.map(() => i++)
.map(function(val){
return Rx.Observable.create(obs => {
obs.next(val)
});
})
.take(10)
.concatAll();
obs.subscribe(function(v){
console.log(v);
});
I would have expected the logged result to be something like:
[3,4,5,6,7,8,9,10,11,12]
That is, 10 values, starting with 3.
However, all we get is just
3
Does anybody know why that would be?

concatMap will wait for the first observable to complete before subscribing to the next. You forgot to add the .complete() to your inner observable, effectively having your stream only emit the first value 3 and waiting indefinitely for the first stream to complete before concatting the next to it.
Note; for a simple value emission as per your question you can also use Rx.Observable.of() instead of Rx.Observable.create()
var i = 3;
const obs = Rx.Observable.interval(10)
.map(() => i++)
.map(val => Rx.Observable.of(val))
.take(10)
.concatAll();
obs.subscribe(v => console.log(v));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.0.3/Rx.js"></script>

Related

In rxjs how can I create groups of sequential values?

I have a stream of values in this form:
[1,2,3,1,2,1,1,1,2...]
I would like to convert it to a stream of groups in the form:
[[1,2,3],[1,2],[1],[1],[1,2]...]
New group should be created every time value becomes 1.
You can use the bufferWhen() operator to collect emitted values until an observable emits a value. In this example I use a subject to emit a shallow copy of the stream to the buffer.
The buffer will emit when ever the number 1 is emitted. If the stream starts with 1 then an empty array is emitted. So I filter that away.
const {from, Subject} = rxjs;
const {filter, bufferWhen, tap, skip} = rxjs.operators;
const stream = from([1,2,3,1,2,1,1,1,2]);
const trigger = new Subject();
stream.pipe(
tap(v => trigger.next(v)),
bufferWhen(() => trigger.pipe(filter(v => v === 1))),
filter(v => v.length)
).subscribe(x => console.log(x));
<script src="https://unpkg.com/#reactivex/rxjs#6.x/dist/global/rxjs.umd.js"></script>
You can use the scan() if you wanted to emit the trigger when the value decreases from the previous. Which is maybe a little better in logic, but using 1 as the trigger fits with the question.
For the record, I'm posting a solution alternative to the accepted answer.
NOTE: For finite streams concat(of(1)) is required before scan(), for distinctUntilChanged to emit the last observable.
const {of, from, ReplaySubject} = rxjs;
const {map, concat, scan, distinctUntilChanged, concatAll, toArray, delay} = rxjs.operators;
//considering infinite stream
const stream = from([1,2,3,1,2,1,1,1,2]);
stream.pipe(
scan( (acc, val) => {
if(val===1){
acc.complete()
acc=new ReplaySubject();
}
acc.next(val);
return acc;
}, new ReplaySubject()),
distinctUntilChanged(),
map(toArray()),
concatAll() );
It would be nice to collect some feedback regarding preferred solution.

Filtering a BehaviorSubject

I have a BehaviorSubject that I'd like to be able to filter, but maintain it's behavior-subject-like quality that new subscribers always get a value when they subscribe, even if the last value emitted was filtered out. Is there a succinct way to do that using built-in functions from rxjs? For example:
const isEven = (n) => n % 2 === 0;
const source = new BehaviorSubject(1);
const stream = source.pipe(filter(isEven));
stream.subscribe((n) => console.log(n)); // <- I want this to print `1`
source.next(2); // prints `2`; that's good
source.next(3); // does not print anything; that's good
I've written my own implementation, but would prefer a simpler solution using existing operators instead if it's easy.
Just use a second BehaviorSubject
const { BehaviorSubject } = rxjs;
const { filter} = rxjs.operators;
const isEven = (n) => n % 2 === 0;
const source = new BehaviorSubject(1);
const stream = new BehaviorSubject(source.getValue());
source.pipe(filter(isEven)).subscribe(stream);
stream.subscribe(val => { console.log(val); });
source.next(2);
source.next(3);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.4.0/rxjs.umd.min.js"></script>
Adrian's answer gets the credit, it looks like he answer the best way given the built-in operators available with rxjs itself. It didn't quite meet my needs, so I published my custom operator in my little library s-rxjs-utils. It it called filterBehavior(). From the docs:
Works like filter(), but always lets through the first emission for each new subscriber. This makes it suitable for subscribers that expect the observable to behave like a BehaviorSubject, where the first emission is processed synchronously during the call to subscribe() (such as the async pipe in an Angular template).
Your stream has already been piped to use the isEven filter, so your initial value of 1 is not shown in your console is behaving as expected.
If you want to see your initial value of 1, subscribe directly to the BehaviourSubject:
const isEven = (n) => n % 2 === 0;
const source = new BehaviorSubject(1);
const stream = source.pipe(filter(isEven));
// should print 1, and should print 2 and 3 when your source is nexted.
source.subscribe((n) => console.log(n));
stream.subscribe((n) => console.log(n)); // <- should NOT Print 1, because it has been filtered
source.next(2); // prints `2`; that's good
source.next(3); // does not print anything; that's good

Delay first 3 items by 1 second and the fourth by 4 seconds

I'm trying to display the first 3 numbers each one delayed by 1 second and the fourth one by 4 seconds. Unfortunately, my code displays the four numbers by 1 second
import { from, of, race, timer, interval } from 'rxjs';
import { groupBy, mergeMap, toArray, map,merge, reduce, concatMap, delay, concat, timeout, catchError, take } from 'rxjs/operators';
const obs$ = from([1,2,3]).pipe(concatMap(a => of(a).pipe(delay(1000))));
const obs2$ = of(4).pipe(delay(4000));
const result$ = obs$.pipe(merge(obs2$));
const subscribe = result$.subscribe(val => console.log(val));
It displays
1234|
instead of
123----4|
this question is entirely for learning rxjs as beginner and has been tested on https://stackblitz.com
METHOD 1
The merge operator subscribes to both observable (obs$ and obs2$) at the same time. Therefore the result you get from your code may be explained as follows:
obs$ -----1-----2-----3
obs2$ -----------------------4
result$ -----1-----2-----3-----4
You may achieve your goal by forcing merge to subscribe only one observable at a time by providing the second argument as 1 (which is Number.POSITIVE_INFINITY by default) like the following:
const obs$ = from([1,2,3]).pipe(concatMap(a => of(a).pipe(delay(1000))));
const obs2$ = of(4).pipe(delay(4000));
// Provide the concurrency (second) argument as 1
const result$ = obs$.pipe(merge(obs2$, 1));
const subscribe = result$.subscribe(val => console.log(val));
METHOD 2
Use concat instead of merge:
const obs$ = from([1, 2, 3]).pipe(concatMap(a => of(a).pipe(delay(1000))));
const obs2$ = of(4).pipe(delay(4000));
const result$ = concat(obs$, obs2$);
const subscribe = result$.subscribe(val => console.log(val));
METHOD 3
Otherwise you simply make use of the second parameter of concatMap which is the index (starts from 0) of emitted items.
const obs$ = from([1, 2, 3, 4]);
const delayed$ = obs$.pipe(
concatMap((value, index) => {
if (index <= 2) {
// Delay the first 3 items by 1 sec
return of(value).pipe(delay(1000));
} else {
// Delay other items (here the 4th item) by 4 sec
return of(value).pipe(delay(4000));
}
})
);
delayed$.subscribe(x => console.log(x));

Padding zip() with undefined

The following
Rx.Observable.zip(
Rx.Observable.of(1,2),
Rx.Observable.of("a"))
.subscribe(p => console.log(p))
produces
1,a
which makes sense, but what I want it to produce is
1,a
2,undefined
I want to pad the shorter observable with undefineds until the longer one completes. Any suggestions?
I realise adding delay can turn the .of operator async and with scan you can replace the same value with undefine
Rx.Observable.combineLatest(
Rx.Observable.of(1,2).delay(0),
Rx.Observable.of("a"))
.scan((acc,curr)=>{
acc[1]=acc[1]==curr[1]?undefined:curr[1]
acc[0]=acc[0]==curr[0]?undefined:curr[0]
return acc
},[])
.subscribe(p => console.log(p))
I think the key to this is to ensure that all of the source observables are the same length.
One solution would be to compose a counter observable that's as long as the longest source observable. It could then be concatenated to the shorter source observables, like this:
const pad = (...sources) => Rx.Observable.create(observer => {
// Publish the source observables to make them multicast
// and to allow the subscription order to be managed.
const publishedSources = sources.map(source => source.publish());
// Create an observable that emits an incremented index and
// is as long as the longest source observable.
const counter = Rx.Observable
.merge(...publishedSources.map(
source => source.map((unused, index) => index)
))
.scan((max, index) => Math.max(max, index), 0)
.distinctUntilChanged()
.publish();
// Zip the published sources, contatenating the counter so
// that they are all the same length. When the counter
// emissions are concatenated, they are mapped to undefined.
const subscription = Rx.Observable.zip(...publishedSources.map(
source => source.concat(counter.mapTo(undefined))
)).subscribe(observer);
// Connect the counter and the published sources.
subscription.add(counter.connect());
publishedSources.forEach(
source => subscription.add(source.connect())
);
return subscription;
});
pad(
Rx.Observable.of(1, 2),
Rx.Observable.of("a")
).subscribe(padded => console.log(padded));
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://unpkg.com/rxjs#5/bundles/Rx.min.js"></script>
My own solution is below. The idea is that all the observables are converted into endless streams, starting with the original values, enclosed in objects, and then an infinite number of undefineds. These endless streams are concatted and the result is taken as long as any of the values are original.
const unending = (obs) =>
obs.map(value => ({value}))
.concat(Rx.Observable
.interval(0)
.mapTo(undefined));
const zipPad = (...obss) =>
Rx.Observable.zip(...obss.map(unending))
.takeWhile(p => p.find(v => v))
.map(p => p.map(v => v && v.value));
zipPad(Rx.Observable.of(1,2,3),
Rx.Observable.of("a"),
Rx.Observable.of("x", "y"))
.subscribe(p => console.log(p));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.min.js"></script>
Anyone is welcome to improve on this answer and post their variations here.

concatMap() equivalent but async like in mergeMap()

I have an observable myObservable :
let myObservable = Observable.of(2000, 1000)
With concatMap() : TOTAL TIME = 3000 millis, results in the original order.
myObservable.concatMap(v => Rx.Observable.of(v).delay(v))
// concatMap: 2000, concatMap: 1000
With mergeMap() : TOTAL TIME = 2000 millis, results not in the original order.
myObservable.mergeMap(v => Rx.Observable.of(v).delay(v))
// mergeMap: 1000, mergeMap: 2000
I want a way to get the results in the original order like in concatMap, but calling each nested observable asynchronously instead of waiting for the next nested observable to complete :
// --- The behavior that I want ---
myObservable.myCustomMap(v => Rx.Observable.of(v).delay(v))
// myCustomMap: 2000, myCustomMap: 1000
// TOTAL TIME = 2000 millis
Is there an elegant solution?
Edit : I am looking for a solution that also works if the source (myObservable) is asynchronous, not only for this particular synchronous case.
You should use forkJoin for firing all the observables at the same time.
Here's an example without comments:
const { Observable } = Rx;
const obs$ = Observable
.of(3000, 3000, 1000)
.map(x => Observable.of(x).delay(x));
const allObsWithDelay$ = obs$.toArray();
const result$ = allObsWithDelay$
.switchMap(arr => Observable.forkJoin(arr));
result$
.do(console.log)
.subscribe();
And the same with explanation:
const { Observable } = Rx;
// source observable, emitting simple values...
const obs$ = Observable
.of(3000, 3000, 1000)
// ... which are wrapped into a different observable and delayed
.map(x => Observable.of(x).delay(x));
// use a reduce to build an array containing all the observables
const allObsWithDelay$ = obs$.toArray();
const result$ = allObsWithDelay$
// when we receive the array with all the observable
// (so we get one event, with an array of multiple observables)
.switchMap(arr =>
// launch every observable into this array at the same time
Observable.forkJoin(arr)
);
// display the result
result$
.do(console.log)
.subscribe();
With those values: 3000, 3000, 1000 the whole process is taking 3 seconds (the maximum of them as they're fired at the same time)
Working Plunkr: https://plnkr.co/edit/IRgEhdjCmZSTc6hSaVeF?p=preview
Edit 1: Thanks to #PierreCitror for pointing out toArray which is better than scan :)
I would do it like this:
myObservable
.mergeMap((val, i) => Observable.forkJoin(
Observable.of(i),
Observable.of(v).delay(v)
))
.scan((acc, ([i, result])) => {
acc[i] = result;
return acc;
}, {})
.filter(allResults => {
// Whatever goes here
Object.keys(allResults) // list indices of all finished responses
})
This will accumulate all responses in a single object where each response is assigned an index at which it arrived into mergeMap.
Then in filter you can write whatever logic you want that decides whether the current state should be propagated further (eg. you can wait until a certain number of responses arrived or whatever).

Resources