简体   繁体   中英

RxJS filter() unexpected behaviour

I came across either this strange behaviour or something I don't know yet. For the following,

of(1).pipe(
  filter(_ => false),
  startWith('hello')
).susbcribe(val => console.log(val));

The above code outputs hello in the console.

What I expect is since filter will allow only the successful conditions to go down the operator chain, how come it is able to output hello through startWith()? Is it the expected behaviour?

startWith happens after you have filtered your contents.

of(1).pipe(           // will yield (1)
  filter(_ => false), // will yield ()
  startWith('hello')  // will yield ('hello')
).susbcribe(val => console.log(val));

So, the stream that consists of 1 , filtered so as to not let anything pass is a thing in and of itself . That thing is then "decorated" with the startWith operator, which makes it yield an initial hello .

That new stream is the one you subscribe to!

This is indeed intended behaviour.

Let startWith be above filter in the pipe(...) arguments and you'll see how that changes:

of(1).pipe(           // will yield (1)
  startWith('hello'), // will yield ('hello', 1)
  filter(_ => false)  // will yield ()
).susbcribe(val => console.log(val));

To address the concerns in the comments section, you can think of the pipe chain as nested calls . For instance, in pseudo-code:

A.pipe(B, C, D)

... is equivalent to doing:

D(C(B(A)))

Thus, the following:

of(1).pipe(           // expression A
  filter(_ => false), // expression B
  startWith('hello')  // expression C
).susbcribe(val => console.log(val));

... would translate to:

startWith(         // expression C
    filter(        // expression B
        of(1),     // expression A
        _ => false
    ),
    'hello'
).susbcribe(val => console.log(val))

Or, in a more "imperative" way:

const one = of(1);
const filtered = filter(one, _ => false);
const greeted = startWith(filtered, 'hello');
greeted.subscribe(val => console.log(val));

It then becomes clear that filter can not affect the operators further down the chain!

The startWith() operator just calls the concat() operator. With the start argument as the first observable and the outer observable as the second.

https://github.com/ReactiveX/rxjs/blob/40a2209636a8b4d4884f5d59ad206ae458ad2de4/src/internal/operators/startWith.ts#L68

The concat() operator emits the values for each observable in sequence from left to right . The first observable must emit all values and complete before the next observable is emitted.

For example;

   concat(of('a','b'), of('1', '2')
      .subscribe(val => console.log(val));
   // prints 'a', 'b', '1', '2'

So we can rewrite your example to use concat() instead and generate the same result and this is basically what startWith() is doing internally.

   concat(of('hello'), of(1).pipe(filter(_ => false))
      .subscribe(val => console.log(val));
   // prints "hello"

So startWith() reorders the sequence of observables so that the value is emitted first, but since it is an operator it can only lift the outer observable. Any operators placed in pipe() after startWith() will be applied to the observable that is the result of calling concat() .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM