Using map and flatMap
A common question people often ask is when to use
map instead of
Well, to answer this, you need to ask two questions:
- Are you transforming the elements synchronously or asynchronously?
- Is the relationship between the input and the output elements one-to-one or one-to-many?
On the one hand,
map uses a synchronous function to convert a value to another (possibly changing the type).
And on the other hand,
flatMap uses an asynchronous function that returns a
This difference alone (the return type of the function passed to the operator) should be enough to choose the appropriate operator.
However, another thing to take into account is that
flatMap work with a one-to-one relationship:
mapconverts from one to
Nnumber of values (in the case of
Flux) of type
Publisherwith the same number of elements.
flatMap works with a one-to-many relationship, since each element can generate a
Flux of any number of elements.
But one thing that may not be obvious is how to properly use either
For this, I have two recommendations:
- When using
flatMap, always keep in mind that the order of the elements is not guaranteed.
- You can nest operators. But don’t abuse that, respect the single-responsibility principle.
The first point is often overlooked. But remember, when working with a network or an external resource such as a database asynchronously, we don’t know for sure when the result will come, so the ordering cannot be guaranteed.
flatMapSequential() methods manage this problem by queuing elements to maintain the order if necessary. On the other hand,
flatMapIterable() keep the order because an
Iterable works in a synchronous way, getting the elements from the source sequentially.
About the second point, nothing stops us from nesting
flatMap operators. In fact, sometimes it’s helpful to nest operators, in particular, if a value is needed for more than one operator:
books.flatMap( book -> getSummary(book, "1.0") .flatMap(summary -> getStats(book, summary) ) );
But remember the single-responsibility principle: A function should do only one thing.
Whenever you see a
flatMap operator nested inside another
reports.flatMap( report -> toXLS(report) .flatMap(reportXLS -> save(reportXLS)) );
Ask yourself if it can be refactored to attach the operators in the main sequence. It will make the code more readable and easier to maintain:
reports .flatMap(report -> toXLS(report)) .flatMap(reportXLS -> save(reportXLS)) );
However, sometimes you’ll need to use other operators to do this. The same happens with imperative code inside
flatMap. Most of the time, it can be replaced by other operators. That’s why, in the next module, you’ll learn more Reactor operators.