visit
Want to jump right into code? Go check it .
While reading about Kotlin's coroutines, I thought it would be really helpful
to compare it to other async programming techniques, specifically the
Futures/Promises approach that is widely used in Java.
Do not expect to have a comprehensive view of how coroutines work nor how Java’s
CompletableFuture
or Reactor’s Mono
work. The idea is to pick a quite simple made-up problem, implement it
with the different approaches and libraries, and compare them. This is
an introductory material to the topic.
For the future/promises approach we will use two very popular APIs/libraries for dealing with Asynchronous Programming in Java:
CompletableFuture
and Mono
.CompletableFuture
is available in the standard Java library since JDK 1.8 and got some improvements over the last releases. Because it is part of the standard library it is the default choice for Java libraries willing to support non-blocking processes and is used in some other APIs in standard libraries, like the .Mono
is the CompletableFuture
equivalent in the library. Project Reactor is a quite popular non-blocking reactive library for the JVM. Its adoption has been growing recently, mostly because of its integration into Spring Framework. It has been adopted by other JVM libraries as well.Now let’s see how the implementation works with these libraries. First, an implementation using
CompletableFuture
:fun completableFuture(): CompletableFuture<CombinedResult> {
val orderIdFuture: CompletableFuture<String> = fetchMostRecentOrderId()
return orderIdFuture
.thenCompose { orderId ->
val deliveryCostFuture: CompletableFuture<String> = fetchDeliveryCost(orderId)
val stockInformationFuture: CompletableFuture<String> = fetchStockInformation(orderId)
deliveryCostFuture.thenCombine(stockInformationFuture) { r1, r2 -> CombinedResult(r1, r2) }
}
}
fun fetchDeliveryCost(): CompletableFuture<String> { ... }
fun fetchStockInformation(): CompletableFuture<String> { ... }
Now the
Mono
implementation:fun mono(): Mono<CombinedResult> {
val orderIdFuture: Mono<String> = fetchMostRecentOrderId()
return orderIdFuture
.flatMap { orderId ->
val deliveryCostFuture: Mono<String> = fetchDeliveryCost(orderId)
val stockInformationFuture: Mono<String> = fetchStockInformation(orderId)
deliveryCostFuture.zipWith(stockInformationFuture) { r1, r2 -> CombinedResult(r1, r2) }
}
}
fun fetchDeliveryCost(): Mono<String> { ... }
fun fetchStockInformation(): Mono<String> { ... }
thenCompose
/flatMap
are used to compose two different promises, meaning basically “after this promise is completed, return this other promise”;thenCombine
/zipWith
are used to combine two different promises, meaning basically “give me a new promise that will complete after both promises are completed”.Kotlin coroutines are in essence lightweight threads. Kotlin language provides one high-level constructs, the suspending functions, and one library, the
kotlinx.coroutines
library, in order to deal with asynchronous programming in a way that is very similar to the regular imperative programming most of the developers are used to.Think of it as the Kotlin way of doing
async/await
that is available in other languages such as C# or JavaScript.Now, back to the topic. Here is how an implementation using suspending functions and constructs in
kotlinx.coroutines
library works:suspend fun coroutines(): CombinedResult = coroutineScope {
//Returns a String directly, no future object
val orderId: String = fetchMostRecentOrderId()
//Note the Deferred type indicating this is a future
val deliveryCostFuture: Deferred<String> = async { fetchDeliveryCost(orderId) }
val stockInformationFuture: Deferred<String> = async { fetchStockInformation(orderId) }
//Awaiting for completion of previous parallel executed fuctions
CombinedResult(deliveryCostFuture.await(), stockInformationFuture.await())
}
suspend fun fetchDeliveryCost(): String { ... }
suspend fun fetchStockInformation(): String { ... }
suspend fun
(lines 1, 13, 14). Suspending functions are basically the Kotlin way of in order to advise the compiler that it may run asynchronous operation;
;fetchMostRecentOrderId
which returns a String
directly;async
with a suspending function, we can run it on the background, which returns a Deferred object (that is like a promise) as seen in lines 6 and 7;async function jsAsync() {
const orderId = await fetchMostRecentOrderId(); //returns String
const deliveryCostFuture = fetchDeliveryCost(orderId); //returns Promise
const stockInformationFuture = fetchStockInformation(orderId); //returns Promise
return new CombinedResult(await deliveryCostFuture, await stockInformationFuture);
}
async function fetchDeliveryCost() { ... }
async function fetchStockInformation() { ... }
async
construct, as async functions in JS returns a Promise.I would like to give a huge thank you to the friends and colleagues who provided their kind words and early feedback on this post. You all rock!
Previously published at