RxJS: sequentially concat a variable number of inner Observables - rxjs

I need to sequentially concat a variable number of inner Observables and stop as soon as the first one resolved to a given result.
See example below or here on Stackblitz where I use concatMap, but this works with a fixed number of inner Observables only.
const values = [1, 4, 6, 3, 9];
getRemoteValue(values[0]).pipe(
concatMap(result => result ? of(result) : getRemoteValue(values[1])),
concatMap(result => result ? of(result) : getRemoteValue(values[2])),
concatMap(result => result ? of(result) : getRemoteValue(values[3])),
concatMap(result => result ? of(result) : getRemoteValue(values[4]))
).subscribe(success => console.log(success ? 'found it' : 'failed'));
function getRemoteValue(input: number): Observable<boolean> {
console.log(`checking ${input}`)
// this would be an async remote call in real life
const value = _.random(10);
return of(value === input);
}

import * as _ from 'lodash';
import { Observable, of, from, EMPTY } from 'rxjs';
import { concatMap, single, find } from 'rxjs/operators';
const values = [1, 4, 6, 3, 9];
function getRemoteValue(input: number): Observable<boolean> {
console.log(`checking ${input}`)
// this would be an async remote call in real life
const value = _.random(10);
return of(value === input);
}
from(values).pipe(
concatMap(getRemoteValue),
find((v) => typeof v === 'boolean' && v === true)
)
.subscribe(success => {
console.log('success:', success);
console.log(success ? 'found it' : 'failed');
}
);
stackblitz

Related

How to zip a list emitted by an observable into another observable?

Let's say I have one observable that emits at an interval of 1 sec. I have another observable that emits at an interval of 3 secs. The second observable emits a sequence of numbers. So every time that the second observable emits a sequence of numbers I want it to be zipped with the first observable.
For example:
observable 1
observable 2
zipped
1
[1]
2
[2]
3
[10,20,30,40]
[3, 10]
4
[4, 20]
5
[5, 30]
6
[10,20,30,40]
[6, 40, 10]
7
[7, 20]
If I am not wrong you are searching solution like this:
import { timer, combineLatest, Observable, BehaviorSubject } from 'rxjs';
import { map } from 'rxjs/operators';
const first$ = timer(1000, 1000);
const second$ = new Observable<number[]>( subscriber => {
const intervalId = setInterval( () => {
subscriber.next([10,20,30,40]);
}, 3000)
return () => {
clearInterval(intervalId)
}
});
// This BehaviorSubject is used like
// some kind of "state" and keeps the emited
// array.
const state$ = new BehaviorSubject<number[]>([]);
second$.subscribe(state$);
combineLatest<[number,number[]]>([
first$,
state$
]).pipe(
map(([first, second]) => {
if (second.length > 0) {
return [first, second.shift()]
}
return [first]
})
).subscribe(
(value) => {
console.log(value);
}
);
Or at least your table describes it. Here is working stackblitz url:
https://stackblitz.com/edit/typescript-ufwqyh?file=index.ts

Window before nested emits

I have observable [1,1,1,2,2,1,1], and want to convert it in observable of sums of same elements group, so result would be [3,4,2]. I wrote this code:
from([1, 1, 1, 2, 2, 1, 1])
.pipe(
connect((numbers$) =>
numbers$.pipe(window(checkChange(numbers$)), mergeMap(sumNumbers))
)
)
.subscribe(console.log);
function checkChange(obs$: Observable<number>): Observable<any> {
return obs$.pipe(
pairwise(),
filter(([a, b]) => a !== b),
map(() => "change")
);
}
function sumNumbers(obs$: Observable<number>): Observable<number> {
return obs$.pipe(reduce((acc, n) => acc + n, 0));
}
But it returns observable [5,3,1]. This is because original observable windows after element where change happens, and not before. So it groups by [[1,1,1,2],[2,1],[1]], and not [[1,1,1],[2,2],[1,1]]. How can I fix it?
You could achieve it using other notifier observable that runs before the window observable gets processed.
You could do something like this
const source = from([1, 1, 1, 2, 2, 1, 1]);
const myNotifier = new Subject<number>(); //Subject to be used as notifier
source
.pipe(
connect((shared$) =>
merge(
shared$.pipe(notifyOnChange(myNotifier)), // 1st run the notifier stream
shared$.pipe(window(myNotifier)) // 2nd the windowed one.
)
),
mergeMap(sumNumbers)
)
.subscribe(console.log);
function notifyOnChange<T>(notifier: Subject<T>) {
return (obs$: Observable<T>): Observable<never> =>
obs$.pipe(
distinctUntilChanged(), // only lets through the value if different to previous one
skip(1), // skip the first emission
tap(notifier), // notify the change
ignoreElements() //prevent the next notification propagation
);
}
function sumNumbers(obs$: Observable<number>): Observable<number> {
return obs$.pipe(reduce((acc, n) => acc + n, 0));
}
Cheers
I would use reduce instead to maintain the current value being counted, running count of correct value, and an array of previous counts.
import {from} from 'rxjs';
import {reduce,map} from 'rxjsoperators';
from([1, 1, 1, 2, 2, 1, 1]).pipe(
reduce(
({ prevCounts, prev, count }, cur) => (count === undefined) ? {
prevCounts,
prev : cur,
count : 1
} : (prev === cur) ? {
prevCounts,
prev,
count: count + 1
} : {
prevCounts: [...prevCounts, count],
prev: cur,
count: 1
},
{ prevCounts : [] }
),
// append count to prevCounts array
map(({ prevCounts, count }) => (count) ? [...prevCounts, count] : prevCounts)
).subscribe({
next: x => console.log(x)
});
This code doesn't use array.push because that changes the array in place and while in this case it wouldn't be a bad thing I've learned it's safer to have the functional programming habit of either returning unchanged objects or new objects.

rxjs: extract slice of a collection given start and end matching rules

I'm trying to write a reactive function with rxjs that, given a potentially infinite array:
Rule 1: Skip initial null items
Rule 2: Extract the items between two '*' appearances
Rule 3: If first item after nulls is not an '*', must fail (or return an empty array)
Rule 4: Process no more than N items
Rule 5: If there's no a second '*', must fail (or return an empty array)
So, with N = 10:
Case 1: [null, null, '*', 1, 2, 3, '*', 4, 5] -> [1, 2, 3]
Case 2: [null, null, 1, '*', 2, 3, '*', 4, 5] -> [] // Breaks rule 3
Case 3: [null, null, '*', 1, 2, 3, 4, 5, 6, 7, 8, '*'] -> [] // Breaks rule 5 (second * is at position > N)
For the case 1, there's no problem. But I don't find the set of operator to enforce the rules 3 and 5
This example illustrates the problem:
const { from } = require('rxjs');
const { take, takeWhile, skipWhile, toArray } = require('rxjs/operators');
function *infinite(items) {
for (let i = 0; ; i++) {
yield i < items.length ? items[i] : `fake${i}`
}
}
const extract = ({
source,
limit = 10,
}) => new Promise(resolve => {
source
.pipe(...[
take(limit),
skipWhile(item => item === null),
skipWhile(item => item === '*'),
takeWhile(item => item !== '*'),
toArray(),
])
.subscribe(result => {
resolve(result)
})
})
;(async () => {
console.log(await extract({ source: from(infinite([null, '*', 1, 2, 3, '*', 4, 5, 6])) }))
console.log(await extract({ source: from(infinite([null, 'a', '*', 1, 2, 3, '*', 4, 5, 6])) }))
console.log(await extract({ source: from(infinite([null, '*', 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12])) }))
})()
Edit: I realized the operation:
skipWhile(item => item === '*'),
is not accurate. Should be something like
skipThisSingleItemIfMatchAsteriskOtherwiseFail
A possible solution for your problem is the following. Comments are inline
function extract(c: any[], n: number) {
// first you create an src stream where all the leading nulls are removed
const src = from(c).pipe(
// just take the first n elements
take(n),
// filter to remove the nulls - this can be a problem if you have nulls between the 2 '*'
filter((item) => item !== null),
// share is used to avoid having more than one subscription to this stream
share()
);
const core = src.pipe(
// here we consider all elements until the second '*' is met
// And what about the first '*'? see the rest of the code, there is the explanation
takeWhile((item) => item !== "*", true),
// create an array which cumulates all the values received until the stream completes
toArray(),
// if the array of all elements received is empty or if the last element is not a '*' return []
// else return the elements received a part the last '*'
map((arr) => {
return arr.length === 0
? []
: arr[arr.length - 1] !== "*"
? []
: arr.slice(0, arr.length - 1);
})
);
// this is the stream returned by the extract function
// it starts from the src stream we have created above
return src.pipe(
// the first element is taken
// since the src stream is shared between this stream and the stream we have called "core" and have built above
// then it means that the first element is consumed here and will not be encountered in the "core" stream
first(),
// if the first element is not a '*' an error is thrown
tap((d) => {
if (d !== "*") {
throw new Error("First not null val is not *");
}
}),
// if no error is thrown then we return the stream "core"
concatMap((firstItem) => {
return core;
}),
// if an error is thrown then we return an Observable which emits []
catchError((e) => of([]))
);
}
In order to use this function you can write the following code
const resp = extract(source, 10);
resp.subscribe((d) => {
// do stuff with the result, for instance
console.log(d);
});
Here a stackblitz that reproduces this logic
How about something with custom RxJS operators? Try the following
const { Observable, from } = rxjs;
const { take, filter, reduce } = rxjs.operators;
const onlyIfFirst = predicate => {
let first = true;
return source =>
new Observable(subscriber =>
source.subscribe({
next(value) {
if (first) {
first = false;
if (predicate(value)) {
subscriber.next(value);
} else {
subscriber.next([]);
subscriber.complete();
}
} else {
subscriber.next(value);
}
},
complete() {
subscriber.complete();
}
})
);
};
const toArrayWhen = (predicate, count) => {
let id = 0;
let times = count * 2;
return source =>
source.pipe(
reduce((acc, curr) => {
if (!!id) {
if (predicate(curr)) id++;
if (id < times && !predicate(curr)) {
acc = [...acc, curr];
}
} else {
if (predicate(curr)) id++;
}
return acc;
}, [])
);
};
const input = [null, null, '*', 1, 2, 3, '*', 3, '*', 4, '*'];
from(input)
.pipe(
take(10),
filter(value => value !== null),
onlyIfFirst(value => value === '*'),
toArrayWhen(value => value === '*', 1)
)
.subscribe({
next: value => console.log('Next:', value),
error: error => console.log('Error:', error),
complete: () => console.log('Complete')
});
<script src="https://unpkg.com/rxjs#6.2.2/bundles/rxjs.umd.min.js"></script>
Note: I'm fairly certain the count variable behavior for > 1 is buggy at the moment. But as long as you only need the first instead of values between two asterisks *, it should be find.

Rxjs map only first emition

Is there an operator that can let me map just the first emit ?
something like
import { from } from 'rxjs';
import { mapFirst } from 'rxjs/operators';
const source = from([1, 2, 3, 4, 5]);
const example = source.pipe(mapFirst(val => val + 10));
//output: 11,2, 3, 4, 5
If you want to write a user-land operator to do it:
import { OperatorFunction } from "rxjs";
import { map } from "rxjs/operators";
function mapFirst<T, R>(selector: (value: T) => R): OperatorFunction<T, T | R> {
return map<T, T | R>((value, index) => (index === 0) ? selector(value) : value);
}
And you'd use it as you have in your question.
Use first operator or take(1)
const source = from([1, 2, 3, 4, 5]);
const example = source.pipe(first());

RxJS emit array items over time?

I'm trying to emit simple array values one after another with 500ms in between:
var a = Rx.Observable.from([1,2,3]);
a.interval(500).subscribe(function(b) { console.log(b); });
However, this throws an exception:
Uncaught TypeError: a.interval is not a function.
Three ways to do it, with RxJS version 6 :
1. Using concatMap
import { from, of, pipe } from 'rxjs';
import { concatMap, delay } from 'rxjs/operators';
const array = [1, 2, 3, 4, 5];
from(array)
.pipe(
concatMap(val => of(val).pipe(delay(1000))),
)
.subscribe(console.log);
2. Using zip and interval
import { from, pipe, interval } from 'rxjs';
import { delay, zip} from 'rxjs/operators';
const array = [1, 2, 3, 4, 5];
from(array)
.pipe(
zip(interval(1000), (a, b) => a),
)
.subscribe(console.log);
3. Using interval as source
import { interval, pipe } from 'rxjs';
import { map, take } from 'rxjs/operators';
const array = [1, 2, 3, 4, 5];
interval(1000)
.pipe(
take(array.length),
map(i => array[i])
)
.subscribe(console.log);
As already pointed out by xgrommx, interval is not an instance member of an observable but rather a static member of Rx.Observable.
Rx.Observable.fromArray([1,2,3]).zip(
Rx.Observable.interval(500), function(a, b) { return a; })
.subscribe(
function(x) { document.write(x + '<br \>'); },
null,
function() { document.write("complete"); });
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/2.5.2/rx.all.min.js"></script>
This is how I would do it:
var fruits = ['apple', 'orange', 'banana', 'apple'];
var observable = Rx.Observable.interval(1000).take(fruits.length).map(t => fruits[t]);
observable.subscribe(t => {
console.log(t);
document.body.appendChild(document.createTextNode(t + ', '));
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/2.5.2/rx.all.min.js"></script>
var arrayList = [1,2,3,4,5];
var source = Rx.Observable
.interval(500/* ms */)
.timeInterval()
.take(arrayList.length);
source.subscribe(function(idx){
console.log(arrayList[idx]);
//or document.write or whatever needed
});
Pretty late but a simpler solution would be :
const arr = ["Hi,", "how", "may", "I", "help", "you?"];
Rx.Observable.interval(500)
.takeWhile(_ => _ < arr.length)
.map(_ => arr[_])
.subscribe(_ => console.log(_))
I find Weichhold technique to be the best but that it would gain in clarity of intent by extracting the zipped value outside of the zip:
// assume some input stream of values:
var inputs = Obs.of(1.2, 2.3, 3.4, 4.5, 5.6, 6.7, 7.8);
// emit each value from stream at a given interval:
var events = Obs.zip(inputs, Obs.interval(1000))
.map(val => val[0])
.forEach(console.log);
If you want to release samples over time, you can do something like this
const observable = interval(100).pipe(
scan((acc, value) => [value, ...acc], []),
sampleTime(10000),
map((acc) => acc[0])
);
I had a little different requirement, my array kept updating over time too. So basically I had to implement a queue which I can dequeue at a regular interval, but I didn't want to use an Interval.
If somebody has a need for something like this then probably this solution can help:
I have a function createQueue() that takes the array as an input and returns an Observable which we subscribe for listening to events from the Array at a regular interval.
The function also modifies the 'push()' method of the passes array so that whenever any item is pushed in the array, the Observable would emit.
createQueue(queue: string[]) {
return Observable.create((obs: Observer<void>) => {
const arrayPush = queue.push;
queue.push = (data: string) => {
const returnVal = arrayPush.call(queue, data);
obs.next();
return returnVal;
}
}).pipe(switchMap(() => {
return from([...queue])
.pipe(
concatMap(val => of(val)
.pipe(delay(1000)))
);
}), tap(_ => queue.shift()))
}
Lets say that the array is: taskQueue = [];
So, we need to pass it to the above function and subscribe to it.
createQueue(taskQueue).subscribe((data) => {
console.log('Data from queue => ', data);
});
Now, every time we do taskQueue.push('<something here>'), the subscription will trigger after a delay of "1000ms".
Please note: we should not be assigning a new array to the taskQueue after createQueue() has been called, or else we will loose the modified push().
Here is a dummy example for the above implementation: Test Example
Rx.Observable instance doesn't have interval method http://xgrommx.github.io/rx-book/content/core_objects/observable/observable_instance_methods/index.html. You can use like this.
Rx.Observable.interval(500)
.map(function(v) { return [1,2,3];})
.subscribe(console.log.bind(console));

Resources