Hey, I have a question to the coroutines in Kotlin...
# getting-started
z
Hey, I have a question to the coroutines in Kotlin: I want to implement a rate-limit http client which waits until the rate-limit expired and then goes on with the requests until the limit is exceeded again. My idea is to do this with callback functions. Like this:
Copy code
kotlin
   private val queue: Deque<() -> Unit> = LinkedList()

    fun addJob(req: String, callback: (String) -> Unit) {
        this.queue.offer {
            callback(req)
        }
    }

    //WILL RUN LATER AT AN UNSPECIFIED POINT OF TIME
    fun pullJob() {
        val func = this.queue.poll()
        if (func != null)
            func()
    }
But I understood suspended functions in such way that they're there to get rid of the callbacks. So can I beautify this code with suspended functions? If so, how?
m
What kind of limit do mean? Number of requests per second?
z
Yeah. For example
m
Ok, 2 min. Let me write an example.
z
I basically want to fetch data from the discordapp.com API. They're responding with 3 headers for the rate-limit related stuff. A header showing how many requests you've left, a header showing the unix timestamp when the limit is resetted and a header showing how many requests you've made. I want to do as many requests as I've requests left. Then I want to wait until the limit is resetted and go on with the requests.
m
Ah, ok
I was thinking something like this:
Copy code
fun <T, R> CoroutineScope.rateLimiter(responseChannel: Channel<R>, handleRequest: (T) -> R) = actor<T> {
    for (request in channel) {
       val response = handleRequest(request)
       responseChannel.send(response)
       
       // Rate limiting logic here
       delay(someTime)
    }
}
Hard to read in the thread here, but the idea is that there's an actor receiving messages on a channel. It will only read from the channel when the ratelimit-rules allow it.
The queue is the channel itself.
z
Ah. I see what you've done. And how can I wait for the result when calling it? Do I have to pass a Channel object and do .receive() on it?
m
Yeah, the responses will come in the response channel. You kinda lose the connection between the request and the response though, but you might be able to do something about it.
z
Yeah the lose of the connection can be a big problem. I was hoping that I can do something like this:
Copy code
//"client"
suspend fun doReq(req: Request): Response{
//Add request to the queue and return when the request has been made 
}
//CALLING
async {client.doReq(/** data **/)}.await()
m
Right. One way to do it, is to make the response channel part of the request. Something like:
data class Request<T, R>(val req: T, responseChannel: Channel<R>)
. The actor can then respond on the request's channel:
Copy code
val response = handleRequest(request.req)
request.responseChannel.send(response)
The
doReq
function could then be implemented like this:
Copy code
val rateLimiter = scope.rateLimiter {
    // Implement request here..
}

suspend fun <T, R> doReq(req: T): R {
    val responseChannel = Channel()
    rateLimiter.send(Request(req, responseChannel))
    return responseChannel.receive()
}
z
Aah okay. That could work 🤔
Do I have to close the channel afterwards or does it do that automatically when it goes out of scope?
m
Hmm, I'm not really sure.
z
Okay perfect. So I guess, I will try to implement it. Thanks 🙂!
m
Cool, good luck 🙂
n
@ZargorNET writing a discord api in kotlin? please share it once it is in some kind of usable state
z
@Nikky I'm sorry, but I don't have any plans to make a full bot library for Kotlin. I just need some API Methods for my OAuth2 login (using Kotlin as a website api backend)
Right now, I can't see really a use for one. There's JDA for the JVM and discord.js for JavaScript. So either way, you're not completely lost when using Kotlin and creating a bot in Kotlin.
m
@ZargorNET There is a
CompletableDeferred
class that you should use instead of the response channel. You only need a single result. https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-completable-deferred/
z
@marstran Oh.That looks exactly like I imagined it.
Copy code
fun request(req: Request): CompletableDeferred<Response> {
        val a = CompletableDeferred<Response>()

        a.complete(httpClient.newCall(req).execute())
        return a
    }

     fun call() {
        val req = Request.Builder().url("url").build()
         runBlocking {
             request(req).await()
         }
    }
(just an example code)