Romman Sabbir
Romman's Blog

Romman's Blog

Kotlin, Coroutine: Convert Async APIs into Sync

Kotlin, Coroutine: Convert Async APIs into Sync

Convert Asynchronous APIs into Synchronous using Kotlin and Coroutine

Romman Sabbir's photo
Romman Sabbir
·Feb 28, 2022·

3 min read

Table of contents

  • What is Async and Sync APIs?
  • How to convert Async APIs into Sync in Kotlin using Coroutine?
  • Let's deep dive into the coding part
  • Full implementation in Kotlin:

What is Async and Sync APIs?

Synchronous/asynchronous APIs are application programming interfaces that return data for requests either immediately or at a later time, respectively. Synchronous/asynchronous APIs provide a way to make immediate or scheduled requests for resources, data or services when available.

The synchronous and asynchronous nature of an API is a function of the time frame from the request to the return of data. In the case of synchronous APIs, the expectation is that there will be an immediate return of data. The application requests data and waits for it until a value is returned.

In the case of asynchronous APIs, the availability of a resource, service or data store may not be immediate. These APIs may provide a callback to the requester when the requested resource is ready. Asynchronous requests are useful in maintaining functionality in an application rather than tie up application resources waiting on a request.

An API may be synchronous where data or service availability, resources and connectivity are high and low latency is a requirement. An API may be asynchronous where data or service availability and connectivity are low or oversaturated with demand.

How to convert Async APIs into Sync in Kotlin using Coroutine?

[Kotlin's Coroutine Knowledge is Required for further]

Kotlin has a lots of modern feature including one suspendCoroutine.

suspend inline fun <T> suspendCoroutine(
    crossinline block: (Continuation<T>) -> Unit
): T

How it's work?

Obtains the current continuation instance inside suspend functions and suspends the currently running coroutine.

In this function both Continuation.resume and Continuation.resumeWithException can be used either synchronously in the same stack-frame where the suspension function is run or asynchronously later in the same thread or from a different thread of execution. Subsequent invocation of any resume function will produce an IllegalStateException. [Official Doc]

Let's deep dive into the coding part

class Worker {
    /*Async API*/
    fun doSomething(listener: Listener, throwError: Boolean) {
        when (throwError) {
            true -> {
                Thread.sleep(3000)
                listener.onError(Exception("Just a random exception..."))
            }
            else -> {
                Thread.sleep(3000)
                listener.onSuccess("Success")
            }
        }
    }

    interface Listener {
        fun onSuccess(msg: String)
        fun onError(e: Exception)
    }
}

We have a class Worker, where we have a single API doSomething(listener : Listener, throwError : Boolean).

Let's take a look at the function signature, function takes a Worker.Listener (Callback) object as a parameter. Client get notifed about the work result through the callback Worker.Listener.

But, it's an Asynchronous API and In some certain cases, we might need to execute the API in a Synchronous way. So, how to do this?

We can solve this problem by the help of Kotlin's Coroutine.

suspend fun Worker.doSomethingSync(throwError: Boolean): String {
    return suspendCoroutine {
        doSomething(object : Worker.Listener {
            override fun onSuccess(msg: String) {
                it.resume("Success")
            }

            override fun onError(e: Exception) {
                it.resumeWithException(e)
            }

        }, throwError)
    }
}

Now, we have an Suspended Extension Function of Worker called doSomethingSync(). This API return a String object as a return type and throw Exception if occurs.

So, where is the magic happening?

doSomethingSync() return suspendCoroutine<String>. Inside the API, we are calling the Async API doSomething() and pass an Anonymous listener as the callback and when callback return events Success or Error, Continuation also return Success or Error. If the state is Error, doSomethingSync() throws Exception. When we call doSomethingSync() API, it's block the current execution and wait for the result from the doSomethingSync() in a Sync way.

Example:

class Tester {
    fun test() {
        val worker = Worker()
        /*Async Operation*/

        /*Callback to get notified regarding the result*/
        val listener = object : Worker.Listener {
            override fun onSuccess(msg: String) {
                println(msg)
            }

            override fun onError(e: Exception) {
                e.printStackTrace()
                println(e.message)
            }
        }
        /*Do some work, don't throw exception*/
        worker.doSomething(listener, false)
        /*Do some work, throw exception*/
        worker.doSomething(listener, true)


        /*Async to Sync Operation*/
        CoroutineScope(Dispatchers.Main).launch {
            try {
                /*Do some work, don't throw exception*/
                println(worker.doSomethingSync(false))
                /*Do some work, throw exception*/
                println(worker.doSomethingSync(true))
            } catch (e: Exception) {
                e.printStackTrace()
                println(e.message)
            }
        }
    }
}

Full implementation in Kotlin:

 
Share this