One of the confusing aspects about working with streams is diving into Rx operators that take a stream and fan out into multiple streams.
Is your head exploding yet?
Let’s dive into a problem I ran into while working on a personal project:
The task at hand is to take a list of GPS moving point data and partition the group data into multiple clusters of points, count up each group, then return the aggregate stats. As a cyclist is moving, I want to know how often they are moving at that specific velocity (speed).
Our weapon of choice is the RxJS groupBy() function, which groups like stream values based on a key value you define.
OK. Easy enough. So my implementation looked something like this:
(point) => point.velocity function determines the
key value for the supplied event, which then 1) creates a new Observable sequence for that specific
key value, if it doesn’t exist, or 2) assigns your event to an existing Observable sequence.
flatMap() to the rescue.
So the story turns to our hero
flatMap(), which as it turns out is specifically tuned to deal with issues of dealing with multiple streams.
flatMap will take a supplied function as its argument, which is the operation to apply to each argument within the supplied stream.
1 2 3 4 5 6
1 2 3
What just happened here?
I specified a merging function for the
flatMap() stream, which performed the
scan() counting aggregation on my group before merging the stream back into the main stream. I threw in a
zip, which annotated my aggregate count value with a record of the group key (velocity) that this value was computed for.
Compare it to imperative
The equivalent of
flatMap in imperative programming is, quite literally, just
_.flatMap(). With a few key differences. Here it is in lodash:
1 2 3 4 5 6 7 8 9 10 11 12
So in the end, the end result was the same with one crucial difference - our Observable, reactive version was able to take intermediate accounts into time and perform an intermediate calculation as data was flowing in. This allowed us to generate an intermediate count for the “0” velocity group.
- When you want to fan out a stream into groups or partitions based on a specific stream value, turn to
- When you have a need to combine a stream-of-streams, you want to look at
flatMap. You may also consider looking at
concatMap, a close cousin of
- Reactive programming gives you more expressive abilities to reason about time and event ordering. You just have to tilt your head a little bit.
Updated typo where the
index variable on a GroupedObservable was changed to correctly be