
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”: launch
, async
, 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.