Home » Kotlin deferred with example

Kotlin deferred with example

Kotlin deferred with example

1. Overview

In this article, let’s discuss the Kotlin deferred with example.

2. Kotlin coroutines

A coroutine is a concurrency design pattern you can use to simplify code that executes asynchronously.

It takes a block of code to run, that works concurrently with the rest of the code. However, it is not bound to any thread. It may suspend its execution in one thread and resume in another one.

Kotlin coroutines are extremely inexpensive when compared with threads. Each time when we want to start a new task asynchronously, we can create a new coroutine. To start a new coroutine, we use one of the main “coroutine builders”: launchasync, or runBlocking.

JetBrains developed the rich kotlinx.coroutines library as part of version 1.3 for coroutines.

3. Kotlin Deferred interface

The Deferred type is similar to CompletableFuture in the sense that it represents a value that is not yet available but probably will be in the future (if no error occurs/exception is thrown).

The syntax of the Kotlin Deferred interface is:

interface Deferred<out T> : Job

Deferred value is a non-blocking cancelable future that extends Job. Job doesn’t return any result however Deferred does.

3.1. Create Deferred using the async builder

We can easily create a Deferred instance with the async extension function on CoroutineScope.

The async creates a coroutine to execute the block of code asynchronously and returns the result of the task. This async returns a reference to the coroutine as a Deferred<T> whereas T refers to the type of the result.

You can wait for the result of the deferred by await method, which throws an exception if the deferred had failed. Note that it also considers a canceled deferred as completed.

fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext, 
    start: CoroutineStart = CoroutineStart.DEFAULT, 
    block: suspend CoroutineScope.() -> T
): Deferred<T>

For example, the following async method returns a string as output.

fun main()  = runBlocking() {
    println("main(): ${Thread.currentThread().name}")
    val deferred: Deferred<String> = async { 
                println("async: ${Thread.currentThread().name}")
                println("async:  ${Thread.currentThread().name}")
                "Hello World"
    }
   val result = deferred.await()
   println(result)
   println("main(): ${Thread.currentThread().name}")
}
/* prints
main(): main @coroutine#1
async corotine: DefaultDispatcher-worker-1 @coroutine#2
async corotine:  DefaultDispatcher-worker-1 @coroutine#2
Hello World
main(): main @coroutine#1 
*/

3.2. Using the Kotlin Completable Deferred class

The async coroutine builder is not the only way to create a Deferred instance.

A CompletableDeferred is closer to ComplatableFuture enabling the user to call complete or exceptionallyComplete.

An instance of completable deferred can be created by CompletableDeferred() function. You must use the public functions complete or cancel to notify the result, or cancellation.

For example, the following OkHttpClient makes a new HTTP call. When the HTTP call returns a response or error, we can notify the CompletableDeferred about the result or error.

fun makeAsyncCall(): Deferred<String> {
    val call = OkHttpClient().newCall(Request.Builder()
            .url("<url goes here>")
            .build())
    val deferred = CompletableDeferred<String>().apply {
        invokeOnCompletion {
            if (isCancelled) {
                call.cancel()
            }
        }
    }
    call.enqueue(object : Callback {
        override fun onResponse(call: Call, response: Response) {
            deferred.complete(response.body()?.string() ?: "Error")
        }
        override fun onFailure(call: Call, e: IOException) {
            deferred.cancel(e)
        }
    })
    return deferred
}

3.2.1. Kotlin Completable Deferred timeout

If you want to set a timeout, you can use the withTimeout function that cancels the block of code after a provided time.

suspend fun getValue(delay: Long): Deferred<String> = coroutineScope {
    async {
        withTimeout(1000) {
            delay(delay) // load and return value, delay just for illustration
            "Result!"
        }
    }
}
fun main() = runBlocking {
    println("Getting values...")
    val v500 = getValue(500)
    val v1100 = getValue(1100)
    println("Now we wait...")
    println(v500.await())
    println(v1100.await())
}
/* prints
Getting values...
Now we wait...
Result!
Exception in thread "main" kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1000 ms
 at (Coroutine boundary. (:-1) 
 at FileKt$getValue$2$1$1.invokeSuspend (File.kt:-1) 
 at FileKt$getValue$2$1.invokeSuspend (File.kt:-1) 
Caused by: kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1000 ms
at kotlinx.coroutines.TimeoutKt .TimeoutCancellationException(Timeout.kt:184)
at kotlinx.coroutines.TimeoutCoroutine .run(Timeout.kt:154)
at kotlinx.coroutines.EventLoopImplBase$DelayedRunnableTask .run(EventLoop.common.kt:502) */

3.3. Create a deferred lazily

By default, the coroutine is immediately scheduled for execution.

However, you can customize the start option by using the start parameter.

For example, the following launch coroutine builder contains the start parameter as CoroutineStart.LAZY to start coroutine lazily. Here, the returned coroutine Deferred is in the new state initially and later we explicitly started the coroutine by invoking the start function of the Deferred.

fun main()  = runBlocking() {
    println("main() method: ${Thread.currentThread().name}")
    val deferred: Deferred<String> = async(Dispatchers.Default, CoroutineStart.LAZY) { 
                println("async corotine: ${Thread.currentThread().name}")
                println("async corotine:  ${Thread.currentThread().name} ")
                "Hello World"
    }
   deferred.start()
   val result = deferred.await()
   println(result)
   println("main() method on ${Thread.currentThread().name}")
}
/* prints
main() method: main @coroutine#1
async corotine: DefaultDispatcher-worker-1 @coroutine#2
async corotine:  DefaultDispatcher-worker-1 @coroutine#2
Hello World
main() method on main @coroutine#1
*/

4. Conclusion

To sum up, we have learned the Kotlin Deferred with example.

Leave a Reply

Your email address will not be published.