smallrye-mutiny
smallrye-mutiny copied to clipboard
New feature: provide a `uni { }` coroutine builder
Context
(Motivated by https://github.com/quarkusio/quarkus/issues/21297)
The existing mutiny-kotlin
module provides extensions methods for converting between Uni/Mutli and coroutines; however, it's missing a straightforward way to return a suspending function as a Uni.
The example in the documentation accomplishes this by using async
:
val deferred: Deferred<String> = GlobalScope.async { "Kotlin ❤ Mutiny" }
val uni: Uni<String> = deferred.asUni()
To accomplish this for normal suspend methods, you'd need to either unnecessarily wrap it async { }
, or create your own emitter
block:
// You either need to wrap it as `async`, which is awkward...
suspend fun returnsUni1(): Uni<String> = async { getFoo() }.asUni()
// Or manually create the uni from `emitter`, like what's done for `Deferred<T>.asUni()`
suspend fun returnsUni2(): Uni<String> = Uni.createFrom().emitter { em: UniEmitter<in T> ->
invokeOnCompletion {
try {
em.complete(getFoo())
} catch (th: Throwable) {
em.fail(th)
}
}
}
suspend fun getFoo(): String {
delay(500)
"Foo"
}
Description
It would use useful to have a uni { }
coroutine builder that lets you return a Uni<T>
from the execution of a coroutine:
suspend fun returnsUni(): Uni<String> = uni { getFoo() }
The implementation would be similar to Deferred<T>.asUni()
, except it would return the result of a block instead of calling getCompleted
:
fun <T> uni(block: suspend () -> T): Uni<T> = Uni.createFrom().emitter { em: UniEmitter<in T> ->
invokeOnCompletion {
try {
em.complete(getCompleted())
} catch (th: Throwable) {
em.fail(th)
}
}
}
Additional details
Use of GlobalScope
The Deferred<T>.asUni()
example uses GlobalScope
, which is generally discouraged from use). In the context of Quarkus you'd presumably want this to run on a Vert.x context.
Using uni { }
outside of a coroutine scope
In my experience, it would also be useful to for the uni { }
builder to support calling a suspending function from a non-suspending function. Sometimes you need to bridge parts of the codebase and would prefer to use coroutines:
fun existingNonSuspendMethod(): Uni<String> = uni { coGetFoo() }
There's a mono { }
coroutine builder for Project Reactor, which makes it easy to return a coroutine as a Mono<T>
AND launch a coroutine from outside a CoroutineScope:
fun returnsMono(): Mono<String> = mono {
coGetFoo()
}
Thanks for raising the idea @rgmz.
Purpose of this library is for sure not, to bridge between "regular" and coroutine code, but I really like the idea of having a uni
builder for sake of usability.
Regarding the documentation, you're right. It was just the shortest code snippet - but won't hurt to add a paragraph about how to properly bridge in a Quarkus app using Vertx.dispatcher()
.
Purpose of this library is for sure not, to bridge between "regular" and coroutine code, but I really like the idea of having a uni builder for sake of usability.
Agreed; my initial motivation was creating uni { }
as a regular coroutine builder, but I got a bit too excited thinking about the edge case. :)
I've updated the original issue, and will open a PR to address the main use case.
This is a bit trickier than anticipated. Coroutines launched inside Uni.createFrom().emitter
won't run, presumably because the block returns immediately.
Most of the "official" extensions under kotlinx.coroutines
implement AbstractCoroutine<T>
, which is an InternalCoroutinesApi
and likely shouldn't be used. One option is to try and get official "mutiny" kotlinx.coroutines bindings. Another option is using GlobalScope
: it works, with the caveat that it breaks structured concurrency and we'd have to explicitly manage the job.
I pushed a rough example, if you're curious: https://github.com/rgmz/smallrye-mutiny/tree/feat/kotlin-uni-builder
Hey @rgmz,
you had to wait long, but now it's here: https://smallrye.io/smallrye-mutiny/2.0.0/guides/kotlin/#uni-builder
Please share your experience and further ideas ;)