Reactor using map() on a Flux does not push elements but .flapMap() does - project-reactor

I suppose even after reading the javadocs multiple times I don't get the difference between map and flatMap apart from the synchronous vs asynchronous transforms.In the following code I don't get any events (it behaves as if the subscribe() was not there.
final Flux<GroupedFlux<String, TData>> groupedFlux =
flux.groupBy(Event::getPartitionKey);
groupedFlux.subscribe(g -> g.delayElements(Duration.ofMillis(100))
.map(this::doWork)
.doOnError(throwable -> log.error("error: ", throwable))
.onErrorResume(e -> Mono.empty())
.subscribe());
However a flapMap() works. This works fine -
final Flux<GroupedFlux<String, TData>> groupedFlux =
flux.groupBy(Event::getPartitionKey);
groupedFlux.subscribe(g -> g.delayElements(Duration.ofMillis(100))
.flatMap(this::doWork)
.doOnError(throwable -> log.error("error: ", throwable))
.onErrorResume(e -> Mono.empty())
.subscribe());
Why is that?
EDIT:
Added sample code for the doWork method as suggested in a comment.
private Mono<Object> doWork(Object event) {
// do some work and possibly return Mono<Object> or
return Mono.empty();
}

I think it's because your doWork method returns a Mono. The map operation implicitly wraps your returned object inside a Mono, so you get a Mono<Mono>. Since your original flow subscribes to the wrapper Mono, but the one inside that one is not subscribed to it never produces anything. In contrast flatMap needs the wrapping to be explicit.
Try modifing your doWork method to return not a Mono and do the explicit Mono.just in the flatMap operation.

Related

How to transform a Mono to a Mono<Void> in case of error

I'm trying to write a method that does something like this
Mono<A> ma = networkCall(); //this might fail
Mono<Void> mv = ma.map( a -> ....) #some logic to perform with `A`
return mv;
The trick is, ma might very well fail and then I would want to just log the situation and return a Mono<Void> that completes with no error.
Looking at the Mono api I just found onErrorResume or onErrorReturn but both would take a function that returns an A (which I can't fabricate), while I would like to return a Void.
I would imagine the solution is quite simple, but couldn't quite find the right operations for this.
So, what operations should I apply to ma to transform it into a Mono<Void> in case of error?
I just found onErrorResume or onErrorReturn but both would take a function that returns an A
onErrorReturn() indeed requires you to return an A, but onErrorResume() just requires you to return a Mono<A>, which can be empty.
So you can use:
doOnNext() to perform your logic with A if the call is successful;
doOnError() to log your error if the call is not successful;
onErrorResume() to return an empty result
then() to convert the result into a Mono<Void>.
Something like:
networkCall()
.doOnNext(a -> doSomethingWith(a))
.doOnError(e -> e.printStackTrace())
.onErrorResume(e -> Mono.empty())
.then();

Why Project Reactor's Mono doesn't have a share() operator?

I'd like to "share" a Mono as I do with Flux.
Flux share() example with Kotlin:
fun `test flux share`() {
val countDownLatch = CountDownLatch(2)
val originalFlux = Flux.interval(Duration.ofMillis(200))
.map { "$it = ${Instant.now()}" }
.take(7)
.share()
.doOnTerminate {
countDownLatch.countDown()
}
println("Starting #1...")
originalFlux.subscribe {
println("#1: $it")
}
println("Waiting ##2...")
CountDownLatch(1).await(1000, TimeUnit.MILLISECONDS)
println("Starting ##2...")
originalFlux.subscribe {
println("##2: $it")
}
countDownLatch.await(10, TimeUnit.SECONDS)
println("End!")
}
I couldn't find a share() operator to Mono. Why doesn't it exist?
I couldn't find a share() operator to Mono. Why doesn't it exist?
The specific behaviour of share() doesn't make much sense with a Mono, but we have cache() which may be what you're after.
share() is equivalent to you calling publish().refcount() on your Flux. Specifically, publish() gives you a ConnectableFlux, or a "hot" flux. (refcount() just automatically connects / stops the flux based on the first / last subscriber.)
The "raison d'Γͺtre" for ConnectableFlux is allowing multiple subscribers to subscribe whenever they wish, missing the data that was emitted before they subscribed. In the case of Mono this doesn't make a great deal of sense, as by definition there is only one value emitted - so if you've missed it, then you've missed it.
However, we do have cache() on Mono, which also turns it into a "hot" source (where the original supplier isn't called for each subscription, just once on first subscribe.) The obvious difference from above is that the value is replayed for every subscriber, but that's almost certainly what you want.
(Sidenote if you test the above - note that you'll need to use Mono.fromSupplier() rather than Mono.just(), as the latter will just grab the value once at instantiation, thus cache() has no meaningful effect.)
From Project Reactor 3.4.x onwards we have Mono#share()
Prepare a Mono which shares this Mono result similar to Flux.shareNext(). This will effectively turn this Mono into a hot task when the first Subscriber subscribes using subscribe() API. Further Subscriber will share the same Subscription and therefore the same result. It's worth noting this is an un-cancellable Subscription.

RxJava2 order of sequence called with compleatable andThen operator

I am trying to migrate from RxJava1 to RxJava2. I am replacing all code parts where I previously had Observable<Void> to Compleatable. However I ran into one problem with order of stream calls. When I previously was dealing with Observables and using maps and flatMaps the code worked 'as expected'. However the andthen() operator seems to work a little bit differently. Here is a sample code to simplify the problem itself.
public Single<String> getString() {
Log.d("Starting flow..")
return getCompletable().andThen(getSingle());
}
public Completable getCompletable() {
Log.d("calling getCompletable");
return Completable.create(e -> {
Log.d("doing actuall completable work");
e.onComplete();
}
);
}
public Single<String> getSingle() {
Log.d("calling getSingle");
if(conditionBasedOnActualCompletableWork) {
return getSingleA();
}else{
return getSingleB();
}
}
What I see in the logs in the end is :
1-> Log.d("Starting flow..")
2-> Log.d("calling getCompletable");
3-> Log.d("calling getSingle");
4-> Log.d("doing actuall completable work");
And as you can probably figure out I would expect line 4 to be called before line 3 (afterwards the name of andthen() operator suggest that the code would be called 'after' Completable finishes it's job). Previously I was creating the Observable<Void> using the Async.toAsync() operator and the method which is now called getSingle was in flatMap stream - it worked like I expected it to, so Log 4 would appear before 3. Now I tried changing the way the Compleatable is created - like using fromAction or fromCallable but it behaves the same. I also couldn't find any other operator to replace andthen(). To underline - the method must be a Completable since it doesn't have any thing meaning full to return - it changes the app preferences and other settings (and is used like that globally mostly working 'as expected') and those changes are needed later in the stream. I also tried to wrap getSingle() method to somehow create a Single and move the if statement inside the create block but I don't know how to use getSingleA/B() methods inside there. And I need to use them as they have their complexity of their own and it doesn't make sense to duplicate the code. Any one have any idea how to modify this in RxJava2 so it behaves the same? There are multiple places where I rely on a Compleatable job to finish before moving forward with the stream (like refreshing session token, updating db, preferences etc. - no problem in RxJava1 using flatMap).
You can use defer:
getCompletable().andThen(Single.defer(() -> getSingle()))
That way, you don't execute the contents of getSingle() immediately but only when the Completablecompletes and andThen switches to the Single.

Is "defer" guaranteed to be called in Swift?

According to Apple Swift documentation defer
This statement lets you do any necessary cleanup that should be performed regardless of how execution leaves the current block of code whether it leaves because an error was thrown or because of a statement such as return or break.
documentation
But this code:
enum SomeError: ErrorType {
case BadLuck
}
func unluckey() throws {
print("\n\tunluckey(πŸ’₯) -> someone will have a bad day ;)\n")
throw SomeError.BadLuck
}
func callsUnluckey() throws {
print("callsUnluckey() -> OPENING something")
defer {
print("callsUnluckey(😎) -> CLOSEING something")
}
print("callsUnluckey() -> WORKING with something")
try unluckey()
print("callsUnluckey() -> will never get here so chill...")
defer {
print("callsUnluckey(πŸ’©) -> why this is not getting called?")
}
}
do {
try callsUnluckey()
} catch {
print("")
print("someone had a bad day")
}
Produces this result in the console:
callsUnluckey() -> OPENING something
callsUnluckey() -> WORKING with something
unluckey(πŸ’₯) -> someone will have a bad day ;)
callsUnluckey(😎) -> CLOSEING something
someone had a bad day
And my question is: why the last defer in callsUnluckey() is not getting called?.
Take a look at the language grammar, as summarized in The Swift Programming Language:defer is a statement. In the grammar, a statement is imperative code to be run in order (as opposed to the definition of a program element, like a class or function, to be later used in imperative code).
Note also the bit right after the part you quoted, on order dependence. If defer was just a declaration, like a function or property or type declaration, there couldn't be an ordering effect. (It doesn't matter what order you put function declarations in, for example.)
IIRC (on mobile right now, can't check easily), the compiler will catch you if you put a defer after a return, noting that it is code that will never be executed.
Remember that "throwing" in Swift is, under the hood, really just a special kind of return type. So if your function throws, no code after the throw will be executed (and thus no defer statement will be able to set up a code block to be later executed). When you declare a function throws, any call in it to another throwing function effectively becomes a possible throw statement, which itself is effectively a return.

How am I meant to use Filepath.Walk in Go?

The filepath.Walk function takes a function callback. This is straight function with no context pointer. Surely a major use case for Walk is to walk a directory and take some action based on it, with reference to a wider context (e.g. entering each file into a table).
If I were writing this in C# I would use an object (with fields that could point back to the objects in the context) as a callback (with a given callback method) on it so the object can encapsulate the context that Walk is called from.
(EDIT: user "usr" suggests that the closure method occurs in C# too)
If I were writing this in C I'd ask for a function and a context pointer as a void * so the function has a context pointer that it can pass into the Walk function and get that passed through to the callback function.
But Go only has the function argument and no obvious context pointer argument.
(If I'd designed this function I would have taken an object as a callback rather than a function, conforming to the interface FileWalkerCallback or whatever, and put a callback(...) method on that interface. The consumer could then attach whatever context to the object before passing it to Walk.)
The only way I can think of doing it is by capturing the closure of the outer function in the callback function. Here is how I am using it:
func ScanAllFiles(location string, myStorageThing *StorageThing) (err error) {
numScanned = 0
// Wrap this up in this function's closure to capture the `corpus` binding.
var scan = func(path string, fileInfo os.FileInfo, inpErr error) (err error) {
numScanned ++
myStorageThing.DoSomething(path)
}
fmt.Println("Scan All")
err = filepath.Walk(location, scan)
fmt.Println("Total scanned", numScanned)
return
}
In this example I create the callback function so its closure contains the variables numScanned and myStorageThing.
This feels wrong to me. Am I right to think it feels weird, or am I just getting used to writing Go? How is it intended for the filepath.Walk method to be used in such a way that the callback has a reference to a wider context?
You're doing it about right. There are two little variations you might consider. One is that you can replace the name of an unused parameter with an underbar. So, in your example where you only used the path, the signature could read
func(path string, _ os.FileInfo, _ error) error
It saves a little typing, cleans up the code a little, and makes it clear that you are not using the parameter. Also, for small functions especially, it's common skip assigning the function literal to a variable, and just use it directly as the argument. Your code ends up reading,
err = filepath.Walk(location, func(path string, _ os.FileInfo, _ error) error {
numScanned ++
myStorageThing.DoSomething(path)
})
This cleans up scoping a little, making it clear that you are using the closure just once.
As a C# programmer I can say that this is exactly how such an API in .NET would be meant to be used. You would be encouraged to use closures and discouraged to create an explicit class with fields because it just wastes your time.
As Go supports closures I'd say this is the right way to use this API. I don't see anything wrong with it.

Resources