javascriptrxjsconcatmap

RXJS - Are nested concapMap equivalent to sequential concatMap?


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...


Solution

  • 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.