0

I am trying to write a Kotlin function that executes a HTTP request, then gives the result back to JavaScript.

Because with the IR compiler I cannot use a suspended function from JavaScript, I am trying to use a callback instead.

However, the callback is never executed when called from a coroutine.

Here's a small sample of what I am doing:

private val _httpClient = HttpClient(JsClient()) {
    install(ContentNegotiation) { json() }
    defaultRequest { url(settings.baseUrl) }
}

fun requestJwtVcJsonCredential(
        request: JSJwtVcJsonVerifiableCredentialRequest,
        callback: (JSDeferredJsonCredentialResponse?, JSJwtVcJsonVerifiableCredentialResponse?, Any?) -> Unit
    ) {
    CoroutineScope(_httpClient.coroutineContext).launch {
        // call suspend function
        val response = requestCredential(convert(request))
        // this never runs, even though the coroutine does run successfully
        println("Coroutine received: $response")
        callback(response.first, response.second, response.third)
    }

}

I've noticed this question had a similar problem in Android, but the suggested fix does not apply to JavaScript... specifically, using a Channel does not help in my case because I don't have a coroutine to receive from, and trying to start a new coroutine to receive from the channel, then calling the callback from that coroutine, also doesn't work (the root problem seems to be that I cannot call a callback function from any coroutine).

What's the best way to solve this problem? Assume the function I need to call is a suspend function (the HTTP Client function) and I cannot change that, but I could change everything around it so that it works from a non-suspend function (as that's a limitation of Kotlin JS).

starball
  • 20,030
  • 7
  • 43
  • 238
Renato
  • 12,940
  • 3
  • 54
  • 85
  • Oh, the suspend function was failing, but silently. I added a `cath(e: Throwable)` and now I can see the function callback is called, but with an error... `q: Illegal input` -> will have to investigate what this means now. – Renato Jan 05 '23 at 10:51

1 Answers1

0

The root problem was that the suspend function was actually failing, but there seems to be no default exception handler so the Exception was not logged anywhere, causing the function to fail silently, making it look like the callback was being called but not executing.

However, I think it's worth it mentioning that KotlinJS supports Promise<T>, so the better way to expose a suspend function to JS is to actually write an "adapter" function that returns a Promise instead.

There is a promise extension function on CouroutineScope which can be used for this.

So, for example, if you've got a Kotlin function like this:

suspend fun makeRequest(request: Request): Response

To expose it in JavaScript you can have an adapter function like this:

@JsExport
fun makeRequestJS(request: Request): Promise<Response> {
    // KTor's HttpClient itself is a CoroutineScope
    return _httpClient.promise { makeRequest(request) }
}

This avoids the need to introduce a callback function.

Renato
  • 12,940
  • 3
  • 54
  • 85