Skyler
Skyler

Reputation: 805

What is rxjs equivalent to Array.some()?

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

Answers (5)

Jonathan Stellwag
Jonathan Stellwag

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 above code only works for arrays emitted as source
  • I expect this to work for you, because of the fact, that your fn's return values synchronous and you could use of
  • If you only want to emit this pipe once I recommend using the take operator.

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>

  • The above code works for single values emitted as source
  • If you only want to emit this pipe once I recommend using the take operator

Upvotes: 0

olivarra1
olivarra1

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

Picci
Picci

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

Derviş Kayımbaşıoğlu
Derviş Kayımbaşıoğlu

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

Poul Kruijt
Poul Kruijt

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

Related Questions