Reactor fireAndForget Mono pass context - java

I am trying to pass subscriber Context to the fireAndForget method which is called inside the doOnNext. The fireAndForget is run also async non-blocking. How this context might be passed so the value for "key" is present? When I run the following test it passes. However, in the logs I can see that for both doOnNext I get:
reactor.core.Exceptions$ErrorCallbackNotImplemented: java.util.NoSuchElementException: Context is empty
#Test
void shouldPassContextToFireAndForget() {
final Mono<String> helloWorldMono = Mono.just("hello")
.doOnNext(this::fireAndForget)
.doOnNext(name -> Mono.deferContextual(contextView -> fireAndForget(contextView, name)).subscribe())
.flatMap(name -> Mono.deferContextual(contextView -> Mono.just(name + " " + contextView.get("key"))))
.contextWrite(Context.of("key", "world"));
StepVerifier.create(helloWorldMono)
.expectNext("hello world")
.verifyComplete();
}
private Mono<String> fireAndForget(ContextView context, String name) {
return Mono.just(name)
.flatMap(value -> Mono.deferContextual(contextView -> Mono.just(value + contextView.get("key"))))
.contextWrite(context);
}
private void fireAndForget(String name) {
Mono.just(name)
.flatMap(value -> Mono.deferContextual(contextView -> Mono.just(value + contextView.get("key"))))
.subscribe();
}

Context is a subscribe-time concept. There are two possible approaches.
You can expose the ContextView at the middle of the chain using transformDeferredContextual:
final Mono<String> helloWorldMono = Mono.just("hello")
.transformDeferredContextual((original, cntx) -> original.doOnNext(name-> fireAndForget(cntx, name).subscribe()))
.flatMap(name -> Mono.deferContextual(contextView -> Mono.just(name + " " + contextView.get("key"))))
.contextWrite(Context.of("key", "world"));
Alternatively, you could take advantage of Mono.deferContextual in order to expose the ContextView at the start of the chain like this:
final Mono<String> helloWorldMono = Mono.deferContextual(context ->
Mono.just("hello")
.doOnNext(name -> fireAndForget(context, name).subscribe())
.flatMap(name -> Mono.just(name + " " + context.get("key")))
).contextWrite(Context.of("key", "world"));

Related

Project reactor - not able to use fallback method

I'm new to reactive programming. I was playing around with fallback methods in case of an error scenario. For this, I referred to this doc https://projectreactor.io/docs/core/release/reference/index.html#_fallback_method and created a sample code.
public static void main(String[] args) {
Flux.just("key1", "key2")
.flatMap(k -> callExternalService(k)
.doOnError(e -> System.out.println("Error scenario"))
.onErrorResume(e -> getFromCache(k)))
.subscribe(value -> System.out.println("value = " + value),
error -> System.out.println("error = " + error));
}
private static Mono<String> callExternalService(String input) {
if(input.equals("key2")) throw new RuntimeException("Mocking the exception");
return Mono.just(input + " - " + LocalDateTime.now());
}
private static Mono<String> getFromCache(String input) {
return Mono.just(input + " ^^ " + LocalDateTime.now());
}
Based on whatever I referred so far, doOnError should print the message in case of the ERROR scenario and onErrorResume should fall back to the other method. But I didn't see the expected outcome->
value = key1 - 2022-05-18T15:58:36.364949
error = java.lang.RuntimeException: Mocking the exception
Please correct me if I'm missing anything.

Understanding Mono.when

I'm new to Spring WebFlux and do not fully understand the Mono.when(). The following code does not work as expected:
List<Mono<Void>> results= ....;
String textVar = "my text";
processors.forEach(p -> {
Mono<Object> restResponseMono = client.getSomething();
results.add(restResponseMono.doOnNext(resp -> {
textVar = textVar + resp.getText();
}).then());
});
Mono.when(results).then(
//here it would expect modification of 'textVar'
Mono.just(textVar);
)
After calling the Mono.when(results).then(...) I would expect all my changes to be applied to the textVar because in the docu it is written:
[...Aggregate given publishers into a new Mono that will befulfilled when all of the given Publishers have completed....]
And the restResponseMono.then() should also wait until everything is completed. So I do not know exactly where is my lack of understanding.
Publishers in the when parameter,When subscribe automatically subscribes.
The way it works is simply this.
Flux<Integer> m1 = Flux.just(1,2).doOnNext(e -> System.out.println("M1 doOnNext: " + e));
Mono<Integer> m2 = Mono.just(12).doOnSuccess(e -> System.out.println("M2 doOnSuccess: " + e));
Mono<String> mono = Mono.when(m1,m2).then(Mono.just("STR")).doOnSuccess(e -> System.out.println("_________when doOnSuccess: " + e));
mono.log().subscribe(System.out::println, Exception::new, () -> System.out.println("Completed2."));

RxJava2: Need help to convert java code into rx

I am newbie in RxJava and need help to improve my code. Here is what I've done:
public Single<List<MenuItemsBlocks>> loadMenuItemsBlocks() {
Completable.fromAction(() -> DataStoreRepository.deleteMenuItemsBlock())
.subscribeOn(Schedulers.io()).blockingAwait();
List<MenuItemsBlocks> blocks = new ArrayList<>();
Set<String> aliasList = getAliasFromMenuItems();
for (String alias : aliasList) {
List<MenuItemsBlocks> itemsBlocks = ApiRepository.getMenuItemBlocks(alias)
.subscribeOn(Schedulers.io())
.flatMapIterable(list -> list)
.map(item -> new MenuItemsBlocks(
item.getId(),
item.getType(),
item.getImagePosition(),
item.getTextField(),
item.getSortOrder(),
item.getFileTimeStamp(),
alias
))
.doOnNext(block -> DataStoreRepository.saveMenuItemsBlock(block))
.subscribeOn(Schedulers.io())
.toList()
.blockingGet();
blocks.addAll(itemsBlocks);
}
return Single.just(blocks);
}
There is no problem at runtime with this code, but I want to improve it in rx style, I've tried to rewrite it something like this (but it's not working):
public Single<List<MenuItemsBlocks>> loadMenuItemsBlocks() {
Completable.fromAction(() -> DataStoreRepository.deleteMenuItemsBlock())
.subscribeOn(Schedulers.io()).blockingAwait();
Set<String> aliasList = getAliasFromMenuItems();
return Observable.fromIterable(aliasList)
.switchMap(alias -> ApiRepository.getMenuItemBlocks(alias)
.subscribeOn(Schedulers.io())
.flatMapIterable(list -> list)
.map(item -> new MenuItemsBlocks(
item.getId(),
item.getType(),
item.getImagePosition(),
item.getTextField(),
item.getSortOrder(),
item.getFileTimeStamp(),
alias
))
.doOnNext(block -> DataStoreRepository.saveMenuItemsBlock(block))
.subscribeOn(Schedulers.io())
.toList()
);
}
And I am stuck with it and need your help!
First of all, if you have blockingAwait in non-test code, you are doing it wrong. Second, you probably need concatMap instead of switchMap as it will just keep switching to later list elements, cancelling the outstanding API calls.
public Single<List<MenuItemsBlocks>> loadMenuItemsBlocks() {
return Completable.fromAction(() -> DataStoreRepository.deleteMenuItemsBlock())
.subscribeOn(Schedulers.io())
.andThen(Single.defer(() -> {
Set<String> aliasList = getAliasFromMenuItems();
return Observable.fromIterable(aliasList)
.concatMap(alias -> ApiRepository.getMenuItemBlocks(alias)
.subscribeOn(Schedulers.io())
.flatMapIterable(list -> list)
.map(item -> new MenuItemsBlocks(
item.getId(),
item.getType(),
item.getImagePosition(),
item.getTextField(),
item.getSortOrder(),
item.getFileTimeStamp(),
alias
))
.doOnNext(block -> DataStoreRepository.saveMenuItemsBlock(block))
.subscribeOn(Schedulers.io())
)
.toList();
}));
}

Rx-Java replace Observable in case of error

I have 2 URLs to fetch the data, for example: \location_1\{userid} and \location_2\{userid}. for the first i get the list of the users and then need to fetch user details by above requests. the issue is that i need to call the \location_1\{userid} and in case there is an error(exception) fetch the data from \location_2\{userid}. is it possible to make it with single rx-chain? i've tried try/catch as described here but looks catch newer calls, only onErrorResumeNext calls.
Observable<List<TestModel2>> observable = apiTest
.performTest()
.flatMapIterable(items -> items)
.flatMap(testModel -> {
try
{
return apiTest.performTest2(testModel.userId);
} catch (Exception e)
{
return apiTest.performTest3(testModel.userId);
}
}).doOnNext(testModel2 -> {Log.d("TestItemData", "doOnNext --- " + testModel2.title);})
.onErrorResumeNext(throwable ->{
Log.d("TestItemData", "onErrorResumeNext -------- ");
return Observable.empty();
})
.toList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
Use onErrorResumeNext (as you already did a bit later in the flow):
Observable<List<TestModel2>> observable = apiTest
.performTest()
.flatMapIterable(items -> items)
.flatMap(testModel ->
apiTest.performTest2(testModel.userId)
.onErrorResumeNext(e -> apiTest.performTest3(testModel.userId)); // <----------------
)
.doOnNext(testModel2 -> {
Log.d("TestItemData", "doOnNext --- " + testModel2.title);
})
.onErrorResumeNext(throwable ->{
Log.d("TestItemData", "onErrorResumeNext -------- ");
return Observable.empty();
})
.toList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());

RxJava concurrency with multiple subscribers and events

I'm looking for a way to attach multiple subscribers to an RxJava Observable stream, with each subscriber processing emitted events asynchronously.
I first tried using .flatMap() but that didn't seem to work on any subsequent subscribers. All subscribers were processing events on the same thread.
.flatMap(s -> Observable.just(s).subscribeOn(Schedulers.newThread()))
What ended up working was consuming each event in a new thread by creating a new Observable each time:
Observable.from(Arrays.asList(new String[]{"1", "2", "3"}))
.subscribe(j -> {
Observable.just(j)
.subscribeOn(Schedulers.newThread())
.subscribe(i -> {
try {
Thread.sleep(ThreadLocalRandom.current().nextInt(100, 500));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("s1=>" + Thread.currentThread().getName() + "=>" + i);
});
});
Output:
s1=>RxNewThreadScheduler-1=>1
s1=>RxNewThreadScheduler-2=>2
s1=>RxNewThreadScheduler-3=>3
And the end result with multiple subscribers:
ConnectableObservable<String> e = Observable.from(Arrays.asList(new String[]{"1", "2", "3"}))
.publish();
e.subscribe(j -> {
Observable.just(j)
.subscribeOn(Schedulers.newThread())
.subscribe(i -> {
try {
Thread.sleep(ThreadLocalRandom.current().nextInt(100, 500));
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println("s1=>" + Thread.currentThread().getName() + "=>" + i);
});
});
e.subscribe(j -> {
Observable.just(j)
.subscribeOn(Schedulers.newThread())
.subscribe(i -> {
try {
Thread.sleep(ThreadLocalRandom.current().nextInt(100, 500));
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println("s2=>" + Thread.currentThread().getName() + "=>" + i);
});
});
e.connect();
Output:
s2=>RxNewThreadScheduler-4=>2
s1=>RxNewThreadScheduler-1=>1
s1=>RxNewThreadScheduler-3=>2
s2=>RxNewThreadScheduler-6=>3
s2=>RxNewThreadScheduler-2=>1
s1=>RxNewThreadScheduler-5=>3
However, this seems a little clunky. Is there a more elegant solution or is RxJava just not a good use case for this?
Use .flatMap(s -> Observable.just(s).observeOn(Schedulers.newThread())....)
if I understood the rx-contract correctly, you are trying to do something, which is against it.
Lets have a look at the contract
The contract of an RxJava Observable is that events ( onNext() , onCompleted() , onEr
ror() ) can never be emitted concurrently. In other words, a single Observable
stream must always be serialized and thread-safe. Each event can be emitted from a
different thread, as long as the emissions are not concurrent. This means no inter‐
leaving or simultaneous execution of onNext() . If onNext() is still being executed on
one thread, another thread cannot begin invoking it again (interleaving). --Tomasz Nurkiewicz in Reactive Programming with RxJava
In my opinion you are trying to break the contract by using a nested subscription in the outer subscription. The onNext call to the subscriber is not serialized anymore.
Why not move the "async"-workload from the subscriber to a flatMap-operator and subscribe to the new observable:
ConnectableObservable<String> stringObservable = Observable.from(Arrays.asList(new String[]{"1", "2", "3"}))
.flatMap(s -> {
return Observable.just(s).subscribeOn(Schedulers.computation());
})
.publish();
stringObservable
.flatMap(s -> {
// do More asyncStuff depending on subscription
return Observable.just(s).subscribeOn(Schedulers.newThread());
})
.subscribe(s -> {
// use result here
});
stringObservable
.subscribe(s -> {
// use immediate result here.
});
stringObservable.connect();
flatMap along with doOnNext on the Observable inside the flatMap will result in the same output as yours.
onNext() is always called in a sequential manner hence using doOnNext after the flatMap will also not work for you. Due to the same reason writing the action inside the final subscribe didn't work in your case.
The below code is written using RxJava2. In version 1 of RxJava you will have to add the try-catch block around Thread.sleep.
ConnectableObservable<String> e = Observable.just("1", "2", "3").publish();
e.flatMap(
s -> Observable.just(s)
.subscribeOn(Schedulers.newThread())
.doOnNext(i -> { // <<<<<<
Thread.sleep(ThreadLocalRandom.current().nextInt(100, 500));
System.out.println("s1=>" + Thread.currentThread().getName() + "=>" + i);
}))
.subscribe();
e.flatMap(
s -> Observable.just(s)
.subscribeOn(Schedulers.newThread())
.doOnNext(i -> { // <<<<<<
Thread.sleep(ThreadLocalRandom.current().nextInt(100, 500));
System.out.println("s2=>" + Thread.currentThread().getName() + "=>" + i);
}))
.subscribe();
e.connect();
You can achieve it with Flowable and parallel:
Flowable.fromIterable(Arrays.asList("1", "2", "3"))
.parallel(3)
.runOn(Schedulers.newThread())
.map(item -> {
try {
Thread.sleep(ThreadLocalRandom.current().nextInt(100, 500));
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println("s1=>" + Thread.currentThread().getName() + "=>" + item);
return Completable.complete();
})
.sequential().subscribe();

Categories

Resources