Mono.Defer() vs Mono.create() vs Mono.just()?
Asked Answered
N

3

44

Could someone help me to understand the difference between:

  • Mono.defer()
  • Mono.create()
  • Mono.just()

How to use it properly?

Notation answered 13/5, 2019 at 15:25 Comment(0)
R
65

Mono.just(value) is the most primitive - once you have a value you can wrap it into a Mono and subscribers down the line will get it.

Mono.defer(monoSupplier) lets you provide the whole expression that supplies the resulting Mono instance. The evaluation of this expression is deferred until somebody subscribes. Inside of this expression you can additionally use control structures like Mono.error(throwable) to signal an error condition (you cannot do this with Mono.just).

Mono.create(monoSinkConsumer) is the most advanced method that gives you the full control over the emitted values. Instead of the need to return Mono instance from the callback (as in Mono.defer), you get control over the MonoSink<T> that lets you emit values through MonoSink.success(), MonoSink.success(value), MonoSink.error(throwable) methods. Reactor documentation contains a few good examples of possible Mono.create use cases: link to doc.

The general advice is to use the least powerful abstraction to do the job: Mono.just -> Mono.defer -> Mono.create.

Ricardoricca answered 13/5, 2019 at 16:55 Comment(3)
Can I say Mono.defer and Mono.create will be executed if there is subscriber? So what about Mono.just? I still don't know when to use it. When I try mono.map(result -> methodA()).switchIfEmpty(Mono.just()), this code will run the mono.just first before map. Actually I still think that switchIfEmpty will be executed after map, because it needs to check that if map return empty then go to switchIfEmpty. But when I try mono.map(result -> methodA()).switchIfEmpty(Mono.create(...)) it will get the desired result flowNotation
You’re correct. The effect of laziness (deferred evaluation) is achieved in Mono.defer and Mono.create through the use of Supplier and Consumer. You don’t pass the actual result to them, instead you pass an expression that once evaluated later gives an expected result. Mono.just is generally used once you already have a computed value or you expect the value to be computed eagerly, immediately when you construct the reactive pipeline.Ricardoricca
In your first example it’s expected that whatever you put in Mono.just, will be calculated before the expression that’s inside of map (which can even never be evaluated if no one subscribes). Mono.create and Mono.deferred will behave as you described.Ricardoricca
O
6

Although in general I agree with (and praise) @IlyaZinkovich's answer, I would be careful with the advice

The general advice is to use the least powerful abstraction to do the job: Mono.just -> Mono.defer -> Mono.create.

In the reactive approach, especially if we are beginners, it's very easy to overlook which the "least powerful abstraction" actually is. I am not saying anything else than @IlyaZinkovich, just depicting one detailed aspect.

Here is one specific use case where the more powerful abstraction Mono.defer() is preferable over Mono.just() but which might not be visible at the first glance.

See also:

We use switchIfEmpty() as a subscription-time branching:

// First ask provider1
provider1.provide1(someData)
    // If provider1 did not provide the result, ask the fallback provider provider2
    .switchIfEmpty(provider2.provide2(someData))

public Mono<MyResponse> provide2(MyRequest someData) {
    // The Mono assembly is needed only in some corner cases
    // but in fact it is always happening
    return Mono.just(someData)
        // expensive data processing which might even fail in the assemble time
        .map(...)
        .map(...)
        ...
}

provider2.provide2() accepts someData only when provider1.provide1() does not return any result, and/or the method assembly of the Mono returned by provider2.provide2() is expensive and even fails when called on wrong data.

It this case defer() is preferable, even if it might not be obvious at the first glance:

provider1.provide1(someData)
    // ONLY IF provider1 did not provide the result, assemble another Mono with provider2.provide()
    .switchIfEmpty(Mono.defer(() -> provider2.provide2(someData)))
Ordain answered 9/11, 2021 at 13:11 Comment(0)
I
0
public Mono<DrawDetail> getDrawDetail(int id, int year) {
return drawDetailCacheService.getDrawDetail(year, id)
    .doOnNext(detail -> LOGGER.debug("draw detail {}-{} found in cache", year, id))
    .switchIfEmpty(
        Mono.defer(() -> {
            LOGGER.debug("draw detail {}-{} not found in cache calling remote...", year, id);
            return Mono.just(callRemoteEventDetailAndSendToCache(id, year));
        })
    );

}

Here's what is difference between Mono.just() and Mono.defer() :

When you use Mono.just(...), the argument inside just is evaluated immediately. This means if you had written:

.switchIfEmpty(Mono.just(callRemoteEventDetailAndSendToCache(id, year)))

The callRemoteEventDetailAndSendToCache(id, year) method would be executed immediately, regardless of whether the cache service returns a value or not. This is definitely not what you'd want, especially if the method has side effects like making remote calls or modifying state.

In contrast, when you use Mono.defer(...), the provided lambda is only executed when the Mono is subscribed to. In the context of switchIfEmpty(), this means the lambda will only execute if the preceding Mono (from the cache service) is empty.

By using Mono.defer(), the callRemoteEventDetailAndSendToCache(id, year) method gets lazily invoked only when needed (i.e., when the cache doesn't have the DrawDetail)

Incondensable answered 11/9, 2023 at 13:37 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Demurral

© 2022 - 2025 — McMap. All rights reserved.