I noticed that when there is a chain of Mono.then(...) ending in Mono.thenReturn(...) the thenReturn() method evaluates first even though it is last in the chain of calls.
In the below example the thenReturn() evaluates before the previous then() calls.
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.WebSession;
import reactor.core.publisher.Mono;
@RestController
public class MainController {
@GetMapping("/")
public Mono<String> index(final WebSession session) {
return session.changeSessionId()
.then(thenTask1())
.then(thenTask2())
.thenReturn(thenReturnTask());
}
private Mono<Void> thenTask1() {
return Mono.create(sink -> {
System.out.println("Performing then task 1");
sink.success();
});
}
private Mono<Void> thenTask2() {
return Mono.create(sink -> {
System.out.println("Performing then task 2");
sink.success();
});
}
private String thenReturnTask() {
System.out.println("Performing then return task");
return "Welcome to this example";
}
}
Is this expected behaviour?
In case you need an example I have created a sample repository to show the issue.
If you run it (./gradlew bootRun) and go to the URL http://localhost:8080/ you will see the output appear like so:
Performing then return task
Performing then task 1
Performing then task 2
Hi @mahanhz,
Thanks for your report!
What you observe is an expected behaviour, but it might be confusing at a first glance.
TL;DR: try adding .doOnSubscribe(s -> System.out.println("Subscribed")) after .thenReturn.
Let me explain what happens:
When you compose your reactive chains, there are two stages:
At this stage, your flow is not executed yet, but you tell Reactor how it should be executed.
Think about it as a "planning" stage. When you plan something, you say things like "I will buy a book" and not (yet) "I am buying a book". That said, when you call thenReturn(thenReturnTask()), it happens before other .then()s because the execution has not started yet.
thenReturn accepts a value and Reactor cannot defer thenReturnTask's code for you.
But .then() accepts Mono which is cold by default. You create it with Mono.create which essentially defers the execution of it and moves it to the execution stage.
This is where all the actions happen ( "walking into a book store", "reading the description", "buying a book", etc).
1) simply ignore it :) Your logging just demonstrates the difference between Assembly and Execution
2) Defer it with .compose() operator:
return session.changeSessionId()
.then(thenTask1())
.then(thenTask2())
.compose(it -> it.thenReturn(thenReturnTask()));
Please note that while it will do the job, this is not efficient. In fact, it will be an equivalent of doing:
return Mono.defer(() -> {
return session.changeSessionId()
.then(thenTask1())
.then(thenTask2())
.thenReturn(thenReturnTask());
});
3) Use the same approach as you were using for thenTask*
return session.changeSessionId()
.then(thenTask1())
.then(thenTask2())
.then(Mono.create(sink -> {
sink.success(thenReturnTask());
}));
Be careful with thenReturnTask's code. It's ok if you just want to log something there, but make sure you're not running any blocking side effects in it. Consider returning Mono if you need to perform them after other tasks get executed.
I hope this helps to understand what happens and gives you a bit of background about the internals of Reactor and functional programming. Please let me know if something remains unclear 馃憤
Most helpful comment
Hi @mahanhz,
Thanks for your report!
What you observe is an expected behaviour, but it might be confusing at a first glance.
TL;DR: try adding
.doOnSubscribe(s -> System.out.println("Subscribed"))after.thenReturn.Background
Let me explain what happens:
When you compose your reactive chains, there are two stages:
Assembly
At this stage, your flow is not executed yet, but you tell Reactor how it should be executed.
Think about it as a "planning" stage. When you plan something, you say things like "I will buy a book" and not (yet) "I am buying a book". That said, when you call
thenReturn(thenReturnTask()), it happens before other.then()s because the execution has not started yet.thenReturnaccepts a value and Reactor cannot deferthenReturnTask's code for you.But
.then()acceptsMonowhich is cold by default. You create it withMono.createwhich essentially defers the execution of it and moves it to the execution stage.Execution
This is where all the actions happen ( "walking into a book store", "reading the description", "buying a book", etc).
Solutions
1) simply ignore it :) Your logging just demonstrates the difference between Assembly and Execution
2) Defer it with
.compose()operator:Please note that while it will do the job, this is not efficient. In fact, it will be an equivalent of doing:
3) Use the same approach as you were using for
thenTask*A word of caution
Be careful with
thenReturnTask's code. It's ok if you just want to log something there, but make sure you're not running any blocking side effects in it. Consider returningMonoif you need to perform them after other tasks get executed.I hope this helps to understand what happens and gives you a bit of background about the internals of Reactor and functional programming. Please let me know if something remains unclear 馃憤