Reputation: 805
If you want to iterate over an array to see if some elements pass a condition, you do something like this:
let result = [1, 34, 65, 3, 7].some(entry => entry > 10)
// result => true
What is the rxjs equivalent for doing this? I imagine its something like this (which isn't totally correct):
// functions return Observable<number>
from([fn1, fn2, fn2]).pipe(
map(fn => fn()),
takeUntil(data > 100)
).subscribe(data => console.log(`result:${data}`)
(The returned value from fn is always asynchronous)
Upvotes: 6
Views: 4533
Reputation: 4267
The 100% rxjs operator equivalent to array.some():
const { of } = rxjs;
const { map } = rxjs.operators;
const some = cb => source => source.pipe(
map(src => src.some(cb))
)
const even = element => element % 2 === 0;
of([1, 2, 3, 4]).pipe(
some(even)
).subscribe(console.log)
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.3/rxjs.umd.min.js"></script>
The rxjs operator equivalent to array.some() implemented with single emits:
const { from } = rxjs;
const { map, scan, filter } = rxjs.operators;
const some = cb => source => source.pipe(
scan((acc, curr) => ([...acc, curr]), []),
map(src => src.some(cb)),
filter(Boolean)
)
const even = element => element % 2 === 0;
from([1, 2, 3, 4]).pipe(
some(even)
).subscribe(console.log)
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.3/rxjs.umd.min.js"></script>
Upvotes: 0
Reputation: 3399
The equivalent of Array.some
, i.e. to check whether if there's at least an element that passes a condition, I think it would be a combination of find
const some = (predicate) => source$ =>
source$.pipe(
find(predicate),
mapTo(true),
defaultIfEmpty(false)
);
This way, as soon as there's an element satisfying the predicate, you'll receive a true (and the observable would complete, as there won't be more values coming). And if none of them match, then you'll receive a false as soon as the source completes.
The upside of using this instead of negating every
is that you can get the true
as soon as it matches the predicate, instead of having to wait for the whole observable to complete.
Extra: note that find
is an existing operator, but it's just sugar for filter(predicate)
+ take(1)
Edit: Oh! I didn't see that you don't have an Observable of numbers, but observable of functions that return observables of numbers.
In that case, you need to flatten it - And using the some
operator described above:
from([fn1,fn2,fn2]).pipe( // where functions return Observable<number>
mergeMap(fn => fn()),
some(data => data > 100)
).subscribe(data => console.log(`result:${data}`)
Or if you don't want to declare your some
operator, just use the internal bits directly:
from([fn1,fn2,fn2]).pipe( // where functions return Observable<number>
mergeMap(fn => fn()),
find(data => data > 100),
mapTo(true),
defaultIfEmpty(false)
).subscribe(data => console.log(`result:${data}`)
Note that you have 2 possible operators for flattening the observables: concatMap
will subscribe to each observable one-by-one, waiting for the previous one to complete before subscribing to the next one (preserving the order of resolved values), while mergeMap
will subscribe to all the observables as they keep coming (but then if a later observable is faster than a previous one, the values emitted won't be in the same order).
Upvotes: 2
Reputation: 17762
There is no direct equivalent to some
operator in RxJs, at least afaik. At the same time it is possible to implement something equivalent, like this
const fn1 = () => of(100).pipe(delay(100))
const fn2 = () => of(200).pipe(delay(200))
const fn3 = () => of(300).pipe(delay(300))
from([fn1,fn2,fn3]).pipe( // where functions return Observable<number>
concatMap(fn=>fn()),
filter(d => d > 100),
toArray()
).subscribe({
next: data => console.log(`Are there elements grearer than 100?`, data.length > 0)
})
Similar result could be obtained using the reduce
RxJs operator.
You have to notice that this implementation requires to scan the entire array of functions before reaching to a conclusive result.
If the array is long, then you may want to exit as soon as you find an element emitted that satisfies the rule. In this case. you can consider to use first
operator, but you have to manage some more complexity: the result has to be dealt by 2 methods of the subscriber, complete
for a success and error
for not success (with some additional logic to understand the nature of the error), and you need also to accept the idea of a statefull implementation, i.e. the use of a variable to hold the value filtered
const fn1 = () => of(100).pipe(delay(100))
const fn2 = () => of(200).pipe(delay(200))
const fn3 = () => of(300).pipe(delay(300))
let found = false;
from([fn1,fn2,fn3]).pipe( // where functions return Observable<number>
concatMap(fn=>fn()),
first(d => d > 100),
tap(() => found = true)
).subscribe({
error: (e) => {
if (e.name === "EmptyError") {
console.log(`Are there elements grearer than 100?`, found)
}
else {
console.error(e)
}
},
complete: () => console.log(`Are there elements grearer than 100?`, found),
})
Upvotes: 0
Reputation: 30575
If you want to iterate over an array to see if all elements pass a condition, you do something like this: let result = [1,34,65,3,7].some(entry=>entry>10) // returns true;
Wrong. you need to use every
to check whether all elements pass a condition or not
some is used if any element sequence satisfies a condition
It is going to be same for RX.js as well.
from([1, 34, 65, 3, 7]).pipe(
every((item) => item < 10),
.... //rest of the operations
)
Upvotes: 0
Reputation: 71911
The equivalent would be the opposite of every
:
from([1, 34, 65, 3, 7]).pipe(
every((item) => item < 10),
map((isEvery) => !isEvery)
).subscribe((e) => console.log(e)) //true
I don't think there is a native some
, but you can always just create it yourself by using those two pipes.
Upvotes: 7