An introduction to Kotlin coroutines compared to Futures/Promises such as Reactor’s Mono or Java’s CompletableFuture
Want to jump right into code? Go check it here.
Introduction
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.
Even though the Kotlin documentation is pretty good and comprehensive, I felt the lack of a more “real world” example. This is an attempt to fill that gap.
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.
Now let’s get into the made-up problem.
Problem Statement
Who never needed to work on a problem where you need to get data from one place, do some processing and send it to a different place? The made-up problem here is basically that.
Assume we work on e-commerce and we want to get the most recent order id from an HTTP endpoint, send this id to two other endpoints that:
- Give us the stock information about the items in the order, and;
- Give us the calculated delivery cost for the order.
Then we need to expose all that information combined to other consumers.
Let’s also assume that the operations after fetching the most recent order are slower on processing and we would like to run them in parallel.
We would like to implement something like the following:
Futures/Promises approach
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 java.net.HTTPClient
.Mono
is the CompletableFuture
equivalent in the Project Reactor 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> { ... }
They are quite similar, right? That’s exactly why they are here under the same section. They both follow the same async programming technique of Futures/Promises.
You can see that the only difference (apart from the return type) are the methods used, even though they have exactly the same mechanics:
/thenCompose
are used to compose two different promises, meaning basically “after this promise is completed, return this other promise”;flatMap
/thenCombine
are used to combine two different promises, meaning basically “give me a new promise that will complete after both promises are completed”.zipWith
We are not going into details of comparing both libraries, but, for basic use cases, you can see that they follow the same approach.
Coroutines approach
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 { ... }
You can see it seems very similar to the imperative programming style most of us are used to and is how we learn coding in the first place. A few notes:
- All functions are suspending functions, as seen by the keywords
(lines 1, 13, 14). Suspending functions are basically the Kotlin way of coloring a function in order to advise the compiler that it may run asynchronous operation;suspend fun
- Suspending functions can only be executed from within another suspending function or a
coroutineScope
builder; - Line 3 is suspended, meaning execution will be suspended (non-blocking!) waiting for the return of
which returns afetchMostRecentOrderId
directly;String
- By using
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
- Line 10 is suspended again until the result of both Deferred objects complete, by calling the await function on the Deferred objects;
- We need to wrap the function into a coroutineScope in order to do parallel processing with async as coroutine code is executed sequentially by default.
Kotlin’s answer to async/await is slightly different from that on JavaScript. For example, here would be a similar function in JS:
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() { ... }
The difference is subtle but important, while Kotlin coroutines are sequential by default, JavaScript ones are concurrent by default:
- On line 2 we need to explicitly use await, while in Kotlin there’s no need to do so;
- On lines 4 and 5 there’s no need to use any
construct, as async functions in JS returns a Promise.async
Conclusion
This is quite a simple flow just to illustrate the differences in programming style/techniques.
One could argue that for this specific example, the code is cleaner and simple to understand using the Futures/Promise approach, and it would be a matter of style. However, as the code grows more complex and we have more and more requirements things can get quite hard to manage with Futures/Promise.
You can check here for a good post with a more complex example comparing the two approaches.
Or even if we just get the same problem and just try to implement it sequentially things will get complicated with Futures/Promises. As seen here, we needed to introduce intermediate types in order to share context, in this case by using Kotlin Pair type.
I’ve also put together a simple Kotlin Spring Boot project where you can run the code yourself and play around with the different techniques discussed here.
This is a brief introduction to the topic and there is much more to get into the coroutines, for this, I would recommend the following material:
- The official Kotlin documentation on coroutines;
- The Asynchronous Programming Techniques page from Kotlin documentation;
- The “Asynchronous programming styles” section of the coroutines design document;
- Why are coroutines implementation are neither like JavaScript one nor like Go one? You can see why here.
Thank you for reading until here and I hope you enjoyed it.
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 https://medium.com/@danilo_barboza/kotlin-asynchronous-programming-styles-compared-347470b95f3a