RxJS: clean way to pass data along observable chains?

Viewed 575

I seem to run into this situation a lot where I am needing to pass data along a chain of streams. In other words, I have obervables that are dependent on the output of one or more other observables.

Below are 3 ways to do this with a simple example, but none of them feel like the "RxJS" way. Is there a better way to do this?

// Method #1 - looks clean, but feels hacky

let firstResponse = null;
performFirstAction().pipe(
  tap(_firstResponse => (firstResponse = _firstResponse)),
  switchMap(_firstResponse => performSecondAction(_firstResponse)),
  switchMap(secondResponse => performThirdAction(firstResponse, secondResponse))
);

// Method #2 - gets ugly real quick as it scales

performFirstAction().pipe(
  switchMap(firstResponse =>
    performSecondAction(firstResponse).pipe(
      map(secondResponse => ({ firstResponse, secondResponse }))
    )
  ),
  switchMap(({ firstResponse, secondResponse }) =>
    performThirdAction(firstResponse, secondResponse)
  )
);

// Method #3 - might as well just use callbacks at this point

performFirstAction().pipe(
  switchMap(firstResponse =>
    performSecondAction(firstResponse).pipe(
      switchMap(secondResponse =>
        performThirdAction(firstResponse, secondResponse)
      )
    )
  )
);
2 Answers

The cleanest and most readable way to do this is to store the intermediary Observables in their own variables.

const firstResponse$ = performFirstAction();

const secondResponse$ = firstResponse$.pipe(
  switchMap(firstResponse => performSecondAction(firstResponse)),
);

const thirdResponse$ = secondResponse$.pipe(
  withLatestFrom(firstResponse$),
  switchMap(([secondResponse, firstResponse]) => performThirdAction(firstResponse, secondResponse))
);

there is another approach for your scenario

performFirstAction().pipe(
  switchMap(first =>
    performSecondAction(first).pipe(mergeMap(second=>performThirdAction(first, second)))
    )
  ),
);

basically you are having first as local variable, so if you have fourth call you can using similar pattern and nest them.

and if you need a common pattern, you try mergeScan by creating higher order function to return observable. Here acc is always the last observable returned value

from([first,first=>second(first),second=>third(second)])
.pipe(mergeScan((acc,currObs)=>currObs(acc),null))
Related