2

When using RXJS I ask myself if nested concatMap are equivalent to sequential ones. Consider the following example:

observable1.pipe(
   concatMap(result1 => observable2.pipe(
      concatMap(result2 => observable3.pipe(
         concatMap(result3 => of(result3)
      )
   )
).susbcribe((result) => {...});

This would result in result being result3. In order to avoid the nesting, I would like to write the same as follows:

of(null).pipe(
   concatMap(() => observable1),
   concatMap(result1 => observable2),
   concatMap(result2 => observable3),
   concatMap(result3 => of(result3))
).susbcribe((result) => {...});

This will result in in result being result3 as well. Even though there might be differences on a detail level, can I assume that both ways to concatenate observable are considered to be equivalent (e.g. with respect to failures)?

Any help is appreciated...

Florian
  • 1,142
  • 1
  • 9
  • 21
  • I can't think of any difference betwwen those two. The flat structure is also more efficient because there are less inner subscriptions but nothing noticeble. Actually, the same would apply to `mergeMap`. – martin Jan 19 '21 at 08:12
  • 2
    The only difference I see is that with the nested version in the inner closure you have all results, `result1` `result2` `result3`, at hand and you can do whatever you want with them. With the second version you can just return `result3` unless you want to define some external variables. – Picci Jan 19 '21 at 09:03

1 Answers1

5

No, they don't behave the same way in all cases.

Nested ConcatMap

observable1.pipe(
  concatMap(result1 => inner2)
)

inner2 = observable2.pipe(
  concatMap(result2 => observable3.pipe(
    concatMap(result3 => of(result3))
  ))
))

will map the next value from observable1 to inner2 when the previous inner2 observable completed.

Sequential ConcatMap

observable1.pipe(
  concatMap(result1 => observable2),
  concatMap(result2 => observable3),
  concatMap(result3 => of(result3))
)

will map the next value from observable1 to observable2 when the previous observable2 completed.

The results you receive in the final subscribe callback will be the same and will arrive in the same order. But the time at which you receive those results may differ because the observable in Nested ConcatMap will probably map to subsequent inner2 observables later than the observable in Sequential ConcatMap will map to subsequent observable2 observables, as inner2 likely takes longer to complete than observable2.

Example

An Example of what events happen in which order using the three observables obs_1, obs_2 and obs_3: https://stackblitz.com/edit/rxjs-mxfdtn?file=index.ts.

With Nested ConcatMap obs_2 and obs_3 are part of the same inner observable (inner2), so the 2nd inner2 is only subscribed to after the 1st inner2 (containing 1st obs_2, 1st obs_3, 2nd obs_3) completed. With Sequential ConcatMap the 2nd obs_2 is subscribed to as soon as the 1st obs_2 completed.

obs_1: --11--12|
obs_2: --21--22|
obs_3: --31--32|

// Nested ConcatMap
   ❶
--11--12|~~~~~~~~~~~~~~~~~12
   │                       └--21--22|~~~22
   │                           │         └--31--32|
   │        ❷             ❸    └--31--32|
   └--21--22|~~~22        |
       │         └--31--32|    
       └--31--32|              

output: --31--32----31--32--------31--32----31--32|


// Sequential ConcatMap
   ❶
--11--12|~~~12
   │         └--21--22|~~~21~~~~~~~~22
   │                       │         └--31--32|
   │        ❷              └--31--32|
   └--21--22|~~~22        ❸
       │         └--31--32|
       └--31--32|

output: --31--32----31--32----31--32----31--32|


x~~~x    x is emitted at the first position but concatMap is holding the value 
    └O   back and waits with ~ until a previous observable completes and then 
         maps the value to the observable O.
❶        start of the 1st obs_2. For Nested ConcatMap this also marks the start of 
         the 1st inner2.

❷        The 1st obs_2 completed but the 1st inner2 hasn't completed yet.
         Nested ConcatMap waits until the 1st inner2 completes before it maps 12 to 
         the 2nd inner2. Sequential ConcatMap will map 12 to the 2nd obs_2 straight away. 

❸        The 1st inner2 completed. Nested ConcatMap maps 12 to the 2nd inner2.
         Sequential ConcatMap has already mapped 12 to the 2nd obs_2 by this time
         and this 2nd obs_2 has already emitted both values and completed but concatMap
         has buffered those values until now.

As you can see the Sequential ConcatMap approach subscribes to the 2nd obs_2 observable earlier than the Nested ConcatMap approach.

frido
  • 13,065
  • 5
  • 42
  • 56
  • I am not sure if I got everything that you tried to explain, but to sum up you say it most likely is a timing aspect (and a scoping aspect as Picci stated) - so no unwanted side effects whatsoever. – Florian Jan 19 '21 at 13:40
  • 1
    @Florian Both approaches can lead to different subscribe times for certain observables. I tried to explain this with an example. Depending on what you're doing with those observables this difference in subscribe time could cause unwanted side effects . – frido Jan 19 '21 at 17:30
  • @friddo Thanks for your detailed explanation. Let me just ask one more question in order to grasp things right. When all observables emit only one value an complete right afterwards, the nested and the sequential way can be considered equal - can't they? From my understanding (for observables emitting only one value) the later observable does not have to wait for more than one value from the previous observable and thus work in the same manner independent from sequential or nested syntax. For observables with more than one emitted value I completely understand what you said. – Florian Jan 21 '21 at 12:59
  • @Florian Yes, if all observable only emit once and complete it makes no difference. You also don't necessarily have to use `concatMap` in that case. `concatMap`, `mergeMap`, `switchMap` and `exhaustMap` all work the same way if all observables involved only emit once. – frido Jan 21 '21 at 20:03