Flux last() operation when empty - java

I am trying solve my problem when i need to get last element (last method) of a flux but in some cases these flux can be empty and the follow error is appear
Flux#last() didn't observe any onNext signal
and this is the chain i have
return apiService.getAll(entry)
.flatMap(response -> {
if (response.getId() != null){
//do some logic
return Mono.just("some Mono");
}
else{
return Mono.empty();
}
})
.last()
//more flatMap operators
I already use switchIfEmpty()as well but can't fix.
What is the correct implementation to verify if can call last() or skip and return a empty to terminate chain operation.
Thanks,

According to Flux.last() api doc:
emit NoSuchElementException error if the source was empty. For a passive version use takeLast(int)
It means that, for an empty upstream Flux:
last() will emit an error
takeLast(1) will return an empty flux
Now, takeLast(1) returns a Flux, not a Mono, as last() does. Then, you can just chain it with Flux.next(), and it will return the only retained value (if any), or propagate the empty signal.
Note: another solution would be to use last().onErrorResume(NoSuchElementException.class, err -> Mono.empty()).
This would catch the error sent by last() internally, and then return an empty mono.
However, if you've got some code other than last() that can throw a NoSuchElementException, you might miss a problem. For this, my personal choice for your case would be to use takeLast(1).next().
The following code example shows behavior of last() vs takeLast(1).next():
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public class FluxLast {
static void subscribe(Mono<?> publisher) {
publisher.subscribe(value -> {},
err -> System.out.println("Failed: " + err.getMessage()),
() -> System.out.println("Completed empty"));
}
public static void main(String[] args) {
subscribe(Flux.empty().last());
subscribe(Flux.empty().takeLast(1).next());
// When not empty, takeLast(1).next() will return the last value
Integer v = Flux.just(1, 2, 3)
.takeLast(1)
.next()
.block();
System.out.println("Last value: "+v);
}
}
Program output:
Failed: Flux#last() didn't observe any onNext signal from Callable flux
Completed empty
3

Related

How can catch MonoError?

I need to catch MonoError and stop an application with ErrorResponse, but the application works as I did not expect.
My code:
return checkText(text)
.then(getWordsFromText(text))
.map(keyWords -> new SuccessfulResponse(keyWords))
.onErrorResume(
throwable -> {
return Mono.just(new ErrorResponse(throwable.getMessage()));
});
public Mono<Void> checkText(String text) {
if (text == null) {
return Mono.error(new Exception("wrong text"));
}
return Mono.empty();
}
my problem is that if text param is null -> I fall into getWordsFromText method. This is an incorrect execution, because if the text parameter is equal to null, then the application must exit with an error (with ErrorResponse).
I fixed it as (replacing 'then' to 'flatMap'):
return checkText(text)
.flatMap(voidParam -> getWordsFromText(text)) //replaced 'then' to 'flatMap'
.map(keyWords -> new SuccessfulResponse(keyWords))
.onErrorResume(
throwable -> {
return Mono.just(new ErrorResponse(throwable.getMessage()));
});
and now it's working correctly. If text param is null I miss the call getWordsFromText method and fall in error handling (onErrorResume).
But I think using flatMap in my case is not a good idea, I don't like how it looks: .flatMap(voidParam -> ...
Can you have any ideas how possible to do better? (without 'flatMap')
In the first snippet, the call to getWordsFromText() is made while building your main reactive pipeline, before it is even subscribed to (i.e. at assembly time). The reason it works as intended in the second snippet is that flatMap only creates the inner publishers (and subsequently subscribes to them) as it receives elements from upstream (i.e. at subscription time).
In this case if you want to replace the flatMap you could try this: .then(Mono.fromCallable(() -> getWordsFromText(text)))

How to return void in stream?

I am haveing List of sending orders.It is increased when method name of parameter is same
But It is not working. Because It hasn't Termination operation
List<SendingOrdres> sendingOrders = new ArrayList<SendingOrdres>();
private void countUpOrResetSendingOrders(String method) {
sendingOrders.stream()
.filter((e) -> {
System.out.println("filter:"+e);
return e.getMethod().equals(method);
})
.peek((e) -> System.out.println("peek:"+e)) //For check
.map((e)->{
int nextNowSendingOrder = e.getNowSendingOrder()+1;
if(nextNowSendingOrder > e.getMaxSendingOrder()) {
e.setNowSendingOrder(0);
}else {
e.setNowSendingOrder(nextNowSendingOrder);
}
return e;
});
// no Termination operation
}
I added Termination operation in upper code. It is working well.
.collect(Collectors.toList());
I have a question.I don't need to return value. So i want to return void.
But If Termination operation hasn't, Stream is not working.
How to return void in stream?
Stream consists of two mandatory (sourcing, terminal) and one optional (intermediate) parts.
Stream:
is generated with sourcing operation (something that creates the Stream<T> instance);
is then optionally continued with one or more, chained intermediate operation(s);
is finally terminated with terminal operation.
void can only be considered to be the return type of the terminal operation (hence, of its lambda (or method reference) expression) in the stream, because every intermediate operation has to return stream, upon which, subsequent intermediate (or terminal) operation would operate.
For example:
List.of(1, 2, 3, 4)
.stream() //sourcing the stream
.forEach(System.out::println); //terminating the stream
is OK, because println just consumes the stream and doesn't have to return another stream.
List.of(1, 2, 3, 4)
.stream() //sourcing the stream
.filter(System.out::println); //ouch..
however, does not compile.
Additionally, beware, that Stream API is lazy, in Java. Intermediate operations are not effectively evaluated, until the terminal operation is executed.

Flux does not wait for completion of elements before 'then'

I am failing to understand the issue and I am not sure what am I doing wrong.
I want to wait for Flux to end and then return Mono of serverResponse
I have attached the code snippet, the doOnNext will populate the categoryIdToPrintRepository.
I have looked around on how to return mono after flux ends and found the 'then' but still the 'then' method is being executed way before the onNextSite is being processed, this results with the error:
java.lang.IllegalArgumentException: 'producer' type is unknown to ReactiveAdapterRegistry
What am I doing wrong?
public Mono<ServerResponse> retrieveCatalog(ServerRequest ignored) {
return Mono.just("start").flatMap(id ->
Flux.fromIterable(appSettings.getSites())
.subscribeOn(ForkJoinPoolScheduler.create("SiteCatalogScheduler"))
.doOnNext(this::onNextSite)
.then(Mono.from(ServerResponse.ok().body(categoryIdToPrintRepository.getSortedTreeValues(), String.class))));
}
private void onNextSite(Integer siteId) {
IntStream.range(1, appSettings.getCatalogMaxValue()).parallel().forEach(catalogId -> {
Optional<SiteCatalogCategoryDTO> cacheData =
siteCatalogCacheUseCaseService.getSiteCatalogResponseFromCache(siteId, catalogId);
cacheData.ifPresentOrElse(siteCatalogCategoryDTO -> {/*do nothing already exist in cache*/},
() -> {
Mono<SiteCatalogCategoryDTO> catalogCategoryDTOMono = WebClient.create(getUri(siteId, catalogId))
.get().retrieve().bodyToMono(SiteCatalogCategoryDTO.class);
catalogCategoryDTOMono.subscribe(siteCatalogCategoryDTO ->
handleSiteServerResponse(siteCatalogCategoryDTO, siteId, catalogId));
});
});
}
private void handleSiteServerResponse(SiteCatalogCategoryDTO siteCatalogCategoryDTO,
int siteId, int catalogId) {
if (siteCatalogCategoryDTO.getResponseStatus().equals(ResponseStatus.SUCCESS))
Flux.fromIterable(siteCatalogCategoryDTO.getMappingList())
.subscribe(mapSCC -> {
categoryIdToPrintRepository.insertIntoTree(mapSCC.getCategoryId(),
"Site " + siteId + " - Catalog " + catalogId + " is mapped to category " + "\"" +
mapSCC.getCategoryName() + "\" (" + mapSCC.getCategoryId() + ")");
siteCatalogCacheUseCaseService.insertIntoSiteCatalogCache(siteId, catalogId, siteCatalogCategoryDTO);
});
}
You are doing several things wrong you should not subscribe in your application, and you are having void methods, which should not be used in reactive programming unless in specific places.
here is some example code:
// Nothing will happen, we are not returning anything, we can't subscribe
private void doSomething() {
Mono.just("Foo");
}
// complier error
doSomething().subscribe( ... );
Your application is a publisher the calling client, is the subscriber, thats why we return a Mono or a Flux out to the calling client, they subscribe.
You have solved it this way:
private void doSomething() {
Mono.just("Foo").subscribe( ... );
}
doSomething();
Now you are subscribing to yourself to get things running, this is not the correct way, as mentioned before, the calling client is the subscriber, not you.
Correct way:
private Mono<String> doSomething() {
return Mono.just("Foo");
}
// This is returned out to the calling client, they subscribe
return doSomething();
As a Mono/Flux completes, it will emit a signal, this signal will trigger the next and the next and the next in the chain.
So my opinion of what you need to do is the following:
Remove all subscribes, if you want to do things there are functions like, flatmap, map, doOnSuccess etc. keep the chain instact all the way out to the client.
Remove all void functions, make sure they return a Flux or a Mono and if you want to not return something return a Mono<Void> by using the Mono.empty() function so that the chain will be complete.
As soon as you use a Mono/Flux you need to handle the return so that others can chain on.
Update:
In order for then to trigger, you must return something, it will return when the previous mono/flux completes.
example:
private Flux<String> doSomething() {
return Flux.just("Foo", "Bar", "FooBar")
.doOnNext(string -> {
return // return something
});
}
// Ignore what was return from doSomething and return something else when the flux has completed (so only trigger on the completed signal from the flux)
return doSomething().then( ... );

Project Reactor: Designing a reactive API

I have a map function which defined as follows: Mono<OUT> map(IN in)
Here's a concrete example:
public Mono<Integer> map(String s) {
return Mono.fromCallable(() -> {
try {
Thread.sleep(1_000); // simulate HTTP request
return 1;
} catch (Exception e) {}
return -1; // need to return something.
});
}
The problem is that in case of an error (i.e. IOException), we still need to return some output. There's also a possibility that there might no be an answer (but no error occurred)
One solution could be an Optional::empty but I think it's cumbersome. Preferably, I'd like to return Mono::empty if an error occurred.
The reason is, Mono::empty gets consumed by the subscriber without any further handling. Here's an example:
Flux.just(
Mono.just("123"),
Mono.empty(),
Mono.just("456")
).flatMap(s -> s)
.subscribe(System.out::println);
The output would be:
123
456
How can achieve the same behaviour?
What should map look like?
EDIT:
Rethinking it, maybe I better off return some container (like Optional) or a custom one (Result) which can be empty.
If I understand correctly, here's what you need:
return Mono.fromCallable(() -> {
Thread.sleep(1_000); // simulate HTTP request
return 1;
}).onErrorResume(_ -> Mono.empty())

Incompatible types: bad return type in lambda expression X cannot be converted to CompetionStage<X>, after adding a layer of thenApply

I get the following error
[ERROR] AccountServiceResource.java:[165,38] incompatible types: bad return type in lambda expression
[ERROR] Response<okio.ByteString> cannot be converted to java.util.concurrent.CompletionStage<Response<okio.ByteString>>
about the following line
return checkExceptionCauses(exception);
where checkedExceptionCauses is a method that returns a Response<ByteString>
private Response<ByteString> checkExceptionCauses(Throwable exception) {
// ...
}
The question, is why it trying to convert it to a CompletionStage<> all of a sudden? Here's (a simplified version of) the original code that compiled fine:
private CompletionStage<Response<ByteString>> getAccountById(RequestContext rc) {
return accountServiceClient.getAccount().thenApply( getAccountResponse -> {
AdAccountResponse payload;
payload.map(getAccountResponse);
return Response.forPayload(serializePayload(payload));
}).exceptionally(exception -> {
LOG.error("Lorem ipsum");
return checkExceptionCauses(exception);
});
}
So you see, we were returning whatever a .thenApply() returned, or an .exceptionally(). (Admittedly, I'm not well-versed in completable futures, so probably that's why I'm confused here.)
But okay, I feel that my modification does the same thing:
private CompletionStage<Response<ByteString>> getAccountById(RequestContext rc) {
return accountServiceClient.getAccount().thenApply( getAccountResponse -> {
AdAccountResponse payload;
payload.map(getAccountResponse);
// *** BEGIN CHANGES *** //
Request salesforceRequest = Request.forUri(FORCEIT_GET_BUSINESS_INFO_URI, "GET").withPayload(businessInfoRequestPayload);
return httpClient.send(salesforceRequest, rc).thenApply(salesforceResponse -> {
if (salesforceResponse.payload().isPresent()) {
// ...
} else {
// ...
}
AdAccountResponse payload;
payload.map(getAccountResponse);
return Response.forPayload(serializePayload(payload));
});
// *** END CHANGES *** //
}).exceptionally(exception -> {
LOG.error("Lorem ipsum");
return checkExceptionCauses(exception);
});
}
All I've done is add another layer of .thenApply(). But I have my inner .thenApply() return the same thing the original code was returning, and my outer .thenApply() just passes it up.
So why am I now all of a sudden getting a complaint about converting to a CompletionStage? I tried this just for kicks:
return CompletableFuture.completedFuture(checkExceptionCauses(exception));
And not surprisingly, I now got a complaint higher up about returning a CompletionStage<Response<ByteString>> instead of a Response<ByteString>.
thenApply is used if you have a synchronous mapping function.
According to the Documentation:
Returns a new CompletionStage that, when this stage completes
normally, is executed with this stage's result as the argument to the
supplied function.
On the other hand, thenCompose is used if you have an asynchronous mapping function that returns a CompletableFuture. In other words, thenCompose returns a future with the result directly, rather than a nested future.
From Documentation:
Returns a new CompletionStage that is completed with the same value as
the CompletionStage returned by the given function.
When this stage completes normally, the given function is invoked with
this stage's result as the argument, returning another
CompletionStage. When that stage completes normally, the
CompletionStage returned by this method is completed with the same
value.
So try to replace thenApply by thenCompose.

Categories

Resources