Reputation: 3
I am confused with the output of the below code, as per the take(3) processing should stop after rendering 10, but still getting 5,6 and 9 from tap operator. Please refer below output and code snippet.
of(null, 20, 15, 10, 5, 6, 9)
.pipe(
tap(val => console.log(`Tapped value ${val}`)),
filterNil(),
take(3)
)
.subscribe(
item => console.log(`Rendering Item ${item}`),
err => console.log(err),
() => console.log('Completed')
);
}
const filterNil = () => (source: Observable<any>) =>
new Observable(observer => {
return source.subscribe({
next(value) {
if (value !== undefined && value !== null) {
observer.next(value);
}
},
error(error) {
observer.error(error);
},
complete() {
observer.complete();
}
});
});
Upvotes: 0
Views: 1488
Reputation: 17762
The final answer to this question
The correct answer to this question is provided by the following post.
My first answer - a simplistic answer
What you see depends on the fact that your code is completely synchronous, and therefore the unsubscribe
after 3 emissions, which is implicit in take(3)
, does not have the chance to run.
Look at this version
of(null, 20, 15, 10, 5, 6, 9)
.pipe(
delay(0), // >>> intruduce a delay
tap(val => console.log(`Tapped value ${val}`)),
filterNil(),
take(3)
)
Here you introduce a delay
, which gives take
the possibility to unsubscribe
and, as a consequence, you see the behavior which you expect.
Not an answer but a more detailed reasoning
I have investigated a bit more into this problem and I have found some things which make my previous answer a bit too simple.
Let's start from the fact that filterNil()
is a legitimate custom operator that should do the same as filter(item => item !== null)
, where filter
is an operator provided by rxjs/operators
, i.e. by the library.
Now, if we substitute filter(item => item !== null)
to filterNil()
in the pipe
we obtain a different result
of(null, 20, 15, 10, 5, 6, 9)
.pipe(
tap(val => console.log(`Tapped value ${val}`)),
filter(item => item !== null),
take(3)
)
.subscribe(
item => console.log(`Rendering Item ${item}`),
err => console.log(err),
() => console.log('Completed')
);
}
// the output on the console is
Tapped value null
Tapped value 20
Rendering Item 20
Tapped value 15
Rendering Item 15
Tapped value 10
Rendering Item 10
Completed
This means that filter(item => item !== null)
and filterNil()
are not equivalent.
The fact that they are not equivalent seems to come from the implementation of the subscribe
method of Observable
joint with the somehow different nature of filterNil
and filter
.
When using filterNil
, the trace of the execution of subscribe
method of Observable is this
If, on the other hand, we use filter
operator, the execution trace of of subscribe
method of Observable is this
Therefore the fact that filterNil
does have the operator
attribute set to null while filter
does have the operator
attribute set to FilterOperator
seems to drive the different behavior. The reasons behind are not clear to me and are worth a new question.
Upvotes: 1
Reputation: 1389
Your filterNil
function is still running because that is a new observable that is not affected by the take
operator. RxJS operators run in order. You can move the take
to the initial position in the pipe, or I would recommend not creating a new observable in your filter function
// operator position matters
.pipe(
take(3)
tap(val => console.log(`Tapped value ${val}`)),
filterNil()
)
// Or re-factored filterNil operator
const filterNilRefactor = () => {
return source => source.pipe(filter((value) => value !== undefined && value !== null))
}
.pipe(
tap(val => console.log(`Tapped value ${val}`)),
filterNilRefactor(),
take(3)
)
Upvotes: 0