What Is tap?
tap mirrors the values emitted by a source observable while also invoking a callback that corresponds to the notification type — next, error, or complete. In practice, when the source emits a normal value, the next callback supplied to tap runs. If the source terminates with an error or a completion signal, the matching error or complete callback is executed instead.
Since tap returns an observable that is identical to the source higher up the pipeline, any actions performed inside its callback do not alter the value delivered downstream. This makes tap particularly well-suited for side effects or debugging.
It is worth noting that while the logic inside tap does not modify the value, an exception thrown inside the handler will break the observable chain and propagate an error to the observer. Moreover, tap is an operator, so it still requires an active subscription to the source. Placing tap at the end of a chain without subscribing will not trigger the source, and therefore none of the side effects inside tap will execute.
The behavior of tap can be summarized as follows:
- Subscribe to the source observable
- When a new value arrives, run the
nextcallback for that value, ignoring any return value - Pass the original value through to the observer
- If the
nextcallback throws, unsubscribe from the source and forward the error notification to the observer - When the source completes, execute the
completecallback and then send the completion notification to the observer - If the source emits an error, run the
errorcallback and forward the error notification to the observer
Typical Use Case
tap is frequently used for side effects. For instance, suppose you need to log every event emitted by an input field and later replay them. The tap operator makes this straightforward:
const input = document.createElement('input');
document.body.appendChild(input);
const events = new ReplaySubject();
fromEvent(input, 'keydown').pipe(
tap((event: any) => {
events.next(event);
})
).subscribe();

