1
votes

I'd like to figure out if Kotlin can replace our current way of dealing with asynchronous code. Right now, we use CompletableFutures to handle asynchronous code. Here is an example of a such a method:

public void onBalanceRequest(Client client, String name) {
  db.fetchBalance(name)
    .thenAccept(balance -> {
       client.sendMessage("Your money: " + balance);
    });
}

The important point here is that onBalanceRequest is called from the main thread, that must not be blocked. Internally, db.fetchBalance runs asynchronous operations and resolves the future on completion, so the given call is not blocking the main thread.

After reviewing the Kotlin docs regarding coroutines, I had hope that we can do something like JavaScript's async/await. For example, this is what we can do in JavaScript:

async function onBalanceRequest(client, name) {
  let balance = await db.fetchBalance(name);
  client.sendMessage("Your money: " + balance);
}

Now, I've tried to connect our existing API to a Kotlin project:

private fun onBalanceRequest(client: Client) = runBlocking {
    val money = db.fetchBalance(client.name)
    client.sendMessage("Your money: $money")
}

suspend fun fetchBalance(player: String): Double? {
    var result: Double? = null
    GlobalScope.launch {
        originalFetchBalance(player).thenAccept {
            result = it
        }
    }.join()
    return result
}

However, since I used runBlocking, the execution of onBalanceRequest is blocking the main thread. So I'm aksing you, if I can achieve something similar to async/await with Kotlin.

Thank you.

2
why do you use runBlocking if you didn't want to block in the first place? - Roland
Because I have no idea what I could try instead. That is the question. This was just an example to visualize the "idea". - K. D.
runBlocking is for blocking code... launch is for non-blocking ;-) - Roland
When replacing runBlocking with launch, my code won't compile. I don't really understand why, but I thought that it can not be used there? - K. D.
what do you mean with "won't compile"? are you using Kotlin 1.3? - Roland

2 Answers

4
votes

If your JS function is async, the corresponding Kotlin function should be suspend:

private suspend fun onBalanceRequest(client: Client) {
    val money = db.fetchBalance(client.name)
    client.sendMessage("Your money: $money")
}

There's no need for await, because Kotlin is statically typed and the compiler already knows which functions are suspend and need to be treated specially (though C#, which is also statically typed, uses async/await model for explicitness).

Note that it can only be called directly from suspend functions; if you want to "fire-and-forget" it, use launch:

private fun onBalanceRequest(client: Client) = GlobalScope.launch {
    val money = db.fetchBalance(client.name)
    client.sendMessage("Your money: $money")
}

And for using your CompletableFuture-returning functions, use kotlinx-coroutines-jdk8:

// should be suggested by IDE
import kotlinx.coroutines.future.await

suspend fun fetchBalance(player: String) = originalFetchBalance(player).await()
2
votes

Of course runBlocking will run blocking. So instead use launch.

private fun onBalanceRequest(client: Client) = GlobalScope.launch {
    val money = db.fetchBalance(client.name)
    client.sendMessage("Your money: $money")
}

To bridge your CompletableFuture, you can use CompletableDeferred then

suspend fun fetchBalance(player: String): Double {
    val async = CompletableDeferred()
    originalFetchBalance(player).whenComplete { (value, error) ->
        if (value != null) async.complete(value)
        if (error != null) async.completeExceptionally(error)
    }
    return async.await()
}