when to use tap() and when to use map() in RxJS
Asked Answered
Q

1

6

A the document describes the tap() with following code:

import { fromEvent } from 'rxjs';
import { tap, map } from 'rxjs/operators';

const clicks = fromEvent(document, 'click');
const positions = clicks.pipe(
  tap(ev => console.log(ev)),
  map(ev => ev.clientX),
);
positions.subscribe(x => console.log(x));

but I do not understand why to use tap. Is it just logging. The explanation says

Map every click to the clientX position of that click, while also logging the click event

Quesada answered 18/2, 2020 at 7:10 Comment(2)
tap() is an operator made to only perform side-effects. Of course you could put the console.log() into map() but that's in general discouraged.Choreography
if you put console.log() instead of ev.clientX - the x would be undefined, such as logging would return undefined. map - data transformation, tap - side effects(would do nothing to your data).Wreckage
J
9

Tap

Perform a side effect for every emission on the source Observable, but return an Observable that is identical to the source

Example:

tap(value => console.log(value))

Map

Applies a given project function to each value emitted by the source Observable, and emits the resulting values as an Observable.

Example:

map(value => 'prefix' + value)

Why not to use map or other operators for side effects like log

You can use map, scan and any other operator that gets a function and returns an individual result to implement side effects. This will make your code very hard to understand/debug/find bugs in the future. Provided that, in general it is not good to use side effects in pipes. Logging can be an exception if you need/want it and don't want to have lot's of subscriptions for every logging point. Therefore tap can be useful. You could adapt this to the following steps:

  1. Try to avoid as many side effects as possible
  2. If you need some (logging) use tap to clarify that there is an side effect

Example for how you could but should not

switchMap(value => {
  console.log(value); // Side effect 1
  const newValue = foo(value);
  console.log(newValue); // Side effect 2
  return of(newValue);
})

Example of how you could do and should

tap(console.log), // Side effect 1
switchMap(value => of(foo(value))),
tap(console.log) // Side effect 2

Last word: When you write initially code that brings not much of an benefit. The larget your project gets and the moment other people try to find bugs it will improve saved time a lot.

Jitters answered 18/2, 2020 at 9:55 Comment(3)
Where would you put arr => arr.sort()? The original array is modified, but would it be good practice to put it in a tap rather than map?Euphemize
Your question is without context and therefore not 100% safe to be correctly answered. But as I can guess you are receiving your arr from some observable and you are inbetween a pipe and want to use this value somehow in some subscription later on. As arr.sort() already returns you the correct sorted array you can use map like in the answer described. If you don't want to use the arr value in a later subscription, I am asking myself why the arr.sort() in general has to be done within the pipe. I would recommend either sticking to rxjs or if you have imperative code - stick to it.Jitters
I mean when the array must be sorted and then used in a later subscription (imagine a list of language choices you want to display in a dropdown - the response does not have them sorted but you want to sort them before displaying). The result would be the same with both map and tap but is there a best practice? Would unnecessarily returning the same instance of the array with map be bad practice? Would introducing changes to the value with tap be bad practice? I guess there should be a preference, because otherwise you would have developers mixing both throughout the project.Euphemize

© 2022 - 2024 — McMap. All rights reserved.