in the link tap, it mentions
"Be careful! You can mutate objects as they pass through the tap operator's handlers."
What is the meaning of this?
I try the following test
obs3 = new Observable<string>((observer) => {
console.log('Observable 3 starts')
observer.next('1');
observer.next('2');
observer.next('3');
observer.next('4');
observer.next('5');
})
ngOnInit() {
this.obs3.pipe(tap(val=>val+1)).subscribe(
val=>{console.log(val);}
);
}
the console output is
Observable 3 starts
1
2
3
4
5
so The observable returned by tap is an exact mirror of the source, but then what is the meaning of the statement
Be careful! You can mutate objects as they pass through the tap operator’s handlers.
?
*************[Edit on 20230707] *******************************
respond with Ruth answer, I try to compare the input observable before tap and output observable after tap by ===, and they are not equals.
const inputObj = {
a: 'test obj prop',
b: 5,
c: ['x', 'y', 'z']
};
of(inputObj) === of(inputObj)
.pipe(
tap((input) => {
input.a = 'changed prop value';
input.b = 60;
input.c = ['1', '2', '3'];
//return input;
})
)? console.log('after tap:still equal'):console.log('after tap: Not equal');
the output is
after tap: Not equal
so this becomes another problem, it violates the API about
The observable returned by tap is an exact mirror of the source
3
Answers
rxjs follows a functional approach. Which means that it encourages the use of "pure functions". "pure functions" should follow the immutability concept, i.e. should receive input, elaborate that input without changing it and return the output. They should not have "side effects", i.e. they should not change anything outside the scope of the function. These concepts should enhance readability, understandability and testability of sw.
At the same time, programs without side effects are basically useless. So there must be a place, usually at the margin of pure function processing, where side effects take place.
The
tap
operator is the operator designed to implement "side effects" in rxjs. In practical termstap
receives an input value, does whatever, and then returns that same input value received. If the input value received is an object, that object can be mutated. Hence the warning you read in the documentation.In you case you do not see any mutation since what you do is not "change the input" (which is a number and therefore can not be changed). The number you get in input is the same one
tap
returns.One more concept: Observers.
What
tap
expects in input is anObserver
. AnObserver
is an object of typewhich is exactly the same input expected by the function
subscribe
. Hence you can imagine thattap
is a sort of subscription placed in the middle of apipe
of operators.The way you have used
tap
andsubscribe
passing to them just one function is a simplified way of usingtap
andsubscribe
and means that you are just passing thenext
function, ignoringerror
andcomplete
.In addition to the excellent explanation by @Picci, I’d like to add why your input didn’t change.
It’s because you passed numbers, not objects, as mentioned in the docs, to the
tap
. Numbers are primitives, and by definition primitives are immutable.However, when you pass an object, you could see how it could be mutated inside the
tap
:This is the same warning as for any other functions receiving objects as arguments as those are passed as references and not as values.
We can design our own
tap
function which would be affected in the same way: