f999730c618af74419408428f68a679e085061bc81b391dfa6520f61d3945e986f94fecdf7034aaaa5184f3bb2896dfa005bd98a684f4576b447469004e2d5 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. import { MonoTypeOperatorFunction, Observer } from '../types';
  2. import { isFunction } from '../util/isFunction';
  3. import { operate } from '../util/lift';
  4. import { createOperatorSubscriber } from './OperatorSubscriber';
  5. import { identity } from '../util/identity';
  6. /**
  7. * An extension to the {@link Observer} interface used only by the {@link tap} operator.
  8. *
  9. * It provides a useful set of callbacks a user can register to do side-effects in
  10. * cases other than what the usual {@link Observer} callbacks are
  11. * ({@link guide/glossary-and-semantics#next next},
  12. * {@link guide/glossary-and-semantics#error error} and/or
  13. * {@link guide/glossary-and-semantics#complete complete}).
  14. *
  15. * ## Example
  16. *
  17. * ```ts
  18. * import { fromEvent, switchMap, tap, interval, take } from 'rxjs';
  19. *
  20. * const source$ = fromEvent(document, 'click');
  21. * const result$ = source$.pipe(
  22. * switchMap((_, i) => i % 2 === 0
  23. * ? fromEvent(document, 'mousemove').pipe(
  24. * tap({
  25. * subscribe: () => console.log('Subscribed to the mouse move events after click #' + i),
  26. * unsubscribe: () => console.log('Mouse move events #' + i + ' unsubscribed'),
  27. * finalize: () => console.log('Mouse move events #' + i + ' finalized')
  28. * })
  29. * )
  30. * : interval(1_000).pipe(
  31. * take(5),
  32. * tap({
  33. * subscribe: () => console.log('Subscribed to the 1-second interval events after click #' + i),
  34. * unsubscribe: () => console.log('1-second interval events #' + i + ' unsubscribed'),
  35. * finalize: () => console.log('1-second interval events #' + i + ' finalized')
  36. * })
  37. * )
  38. * )
  39. * );
  40. *
  41. * const subscription = result$.subscribe({
  42. * next: console.log
  43. * });
  44. *
  45. * setTimeout(() => {
  46. * console.log('Unsubscribe after 60 seconds');
  47. * subscription.unsubscribe();
  48. * }, 60_000);
  49. * ```
  50. */
  51. export interface TapObserver<T> extends Observer<T> {
  52. /**
  53. * The callback that `tap` operator invokes at the moment when the source Observable
  54. * gets subscribed to.
  55. */
  56. subscribe: () => void;
  57. /**
  58. * The callback that `tap` operator invokes when an explicit
  59. * {@link guide/glossary-and-semantics#unsubscription unsubscribe} happens. It won't get invoked on
  60. * `error` or `complete` events.
  61. */
  62. unsubscribe: () => void;
  63. /**
  64. * The callback that `tap` operator invokes when any kind of
  65. * {@link guide/glossary-and-semantics#finalization finalization} happens - either when
  66. * the source Observable `error`s or `complete`s or when it gets explicitly unsubscribed
  67. * by the user. There is no difference in using this callback or the {@link finalize}
  68. * operator, but if you're already using `tap` operator, you can use this callback
  69. * instead. You'd get the same result in either case.
  70. */
  71. finalize: () => void;
  72. }
  73. export function tap<T>(observerOrNext?: Partial<TapObserver<T>> | ((value: T) => void)): MonoTypeOperatorFunction<T>;
  74. /** @deprecated Instead of passing separate callback arguments, use an observer argument. Signatures taking separate callback arguments will be removed in v8. Details: https://rxjs.dev/deprecations/subscribe-arguments */
  75. export function tap<T>(
  76. next?: ((value: T) => void) | null,
  77. error?: ((error: any) => void) | null,
  78. complete?: (() => void) | null
  79. ): MonoTypeOperatorFunction<T>;
  80. /**
  81. * Used to perform side-effects for notifications from the source observable
  82. *
  83. * <span class="informal">Used when you want to affect outside state with a notification without altering the notification</span>
  84. *
  85. * ![](tap.png)
  86. *
  87. * Tap is designed to allow the developer a designated place to perform side effects. While you _could_ perform side-effects
  88. * inside of a `map` or a `mergeMap`, that would make their mapping functions impure, which isn't always a big deal, but will
  89. * make it so you can't do things like memoize those functions. The `tap` operator is designed solely for such side-effects to
  90. * help you remove side-effects from other operations.
  91. *
  92. * For any notification, next, error, or complete, `tap` will call the appropriate callback you have provided to it, via a function
  93. * reference, or a partial observer, then pass that notification down the stream.
  94. *
  95. * The observable returned by `tap` is an exact mirror of the source, with one exception: Any error that occurs -- synchronously -- in a handler
  96. * provided to `tap` will be emitted as an error from the returned observable.
  97. *
  98. * > Be careful! You can mutate objects as they pass through the `tap` operator's handlers.
  99. *
  100. * The most common use of `tap` is actually for debugging. You can place a `tap(console.log)` anywhere
  101. * in your observable `pipe`, log out the notifications as they are emitted by the source returned by the previous
  102. * operation.
  103. *
  104. * ## Examples
  105. *
  106. * Check a random number before it is handled. Below is an observable that will use a random number between 0 and 1,
  107. * and emit `'big'` or `'small'` depending on the size of that number. But we wanted to log what the original number
  108. * was, so we have added a `tap(console.log)`.
  109. *
  110. * ```ts
  111. * import { of, tap, map } from 'rxjs';
  112. *
  113. * of(Math.random()).pipe(
  114. * tap(console.log),
  115. * map(n => n > 0.5 ? 'big' : 'small')
  116. * ).subscribe(console.log);
  117. * ```
  118. *
  119. * Using `tap` to analyze a value and force an error. Below is an observable where in our system we only
  120. * want to emit numbers 3 or less we get from another source. We can force our observable to error
  121. * using `tap`.
  122. *
  123. * ```ts
  124. * import { of, tap } from 'rxjs';
  125. *
  126. * const source = of(1, 2, 3, 4, 5);
  127. *
  128. * source.pipe(
  129. * tap(n => {
  130. * if (n > 3) {
  131. * throw new TypeError(`Value ${ n } is greater than 3`);
  132. * }
  133. * })
  134. * )
  135. * .subscribe({ next: console.log, error: err => console.log(err.message) });
  136. * ```
  137. *
  138. * We want to know when an observable completes before moving on to the next observable. The system
  139. * below will emit a random series of `'X'` characters from 3 different observables in sequence. The
  140. * only way we know when one observable completes and moves to the next one, in this case, is because
  141. * we have added a `tap` with the side effect of logging to console.
  142. *
  143. * ```ts
  144. * import { of, concatMap, interval, take, map, tap } from 'rxjs';
  145. *
  146. * of(1, 2, 3).pipe(
  147. * concatMap(n => interval(1000).pipe(
  148. * take(Math.round(Math.random() * 10)),
  149. * map(() => 'X'),
  150. * tap({ complete: () => console.log(`Done with ${ n }`) })
  151. * ))
  152. * )
  153. * .subscribe(console.log);
  154. * ```
  155. *
  156. * @see {@link finalize}
  157. * @see {@link TapObserver}
  158. *
  159. * @param observerOrNext A next handler or partial observer
  160. * @param error An error handler
  161. * @param complete A completion handler
  162. * @return A function that returns an Observable identical to the source, but
  163. * runs the specified Observer or callback(s) for each item.
  164. */
  165. export function tap<T>(
  166. observerOrNext?: Partial<TapObserver<T>> | ((value: T) => void) | null,
  167. error?: ((e: any) => void) | null,
  168. complete?: (() => void) | null
  169. ): MonoTypeOperatorFunction<T> {
  170. // We have to check to see not only if next is a function,
  171. // but if error or complete were passed. This is because someone
  172. // could technically call tap like `tap(null, fn)` or `tap(null, null, fn)`.
  173. const tapObserver =
  174. isFunction(observerOrNext) || error || complete
  175. ? // tslint:disable-next-line: no-object-literal-type-assertion
  176. ({ next: observerOrNext as Exclude<typeof observerOrNext, Partial<TapObserver<T>>>, error, complete } as Partial<TapObserver<T>>)
  177. : observerOrNext;
  178. return tapObserver
  179. ? operate((source, subscriber) => {
  180. tapObserver.subscribe?.();
  181. let isUnsub = true;
  182. source.subscribe(
  183. createOperatorSubscriber(
  184. subscriber,
  185. (value) => {
  186. tapObserver.next?.(value);
  187. subscriber.next(value);
  188. },
  189. () => {
  190. isUnsub = false;
  191. tapObserver.complete?.();
  192. subscriber.complete();
  193. },
  194. (err) => {
  195. isUnsub = false;
  196. tapObserver.error?.(err);
  197. subscriber.error(err);
  198. },
  199. () => {
  200. if (isUnsub) {
  201. tapObserver.unsubscribe?.();
  202. }
  203. tapObserver.finalize?.();
  204. }
  205. )
  206. );
  207. })
  208. : // Tap was called with no valid tap observer or handler
  209. // (e.g. `tap(null, null, null)` or `tap(null)` or `tap()`)
  210. // so we're going to just mirror the source.
  211. identity;
  212. }