So, the first thing, there is a promise scope: <ht...
# javascript
a
So, the first thing, there is a promise scope: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/[js]promise.html It accepts a suspend block and returns a promise with the return value. To run any function that returns promise in a suspend context - you can use await: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/await.html But, if you have just a simple case (call async function and provide the result to another function), you could use a plain-old Promise.then method:https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.js/-promise/then.html
h
Oh, god damnit, why didn't that pop up when I searched for "kotlinjs Coroutines promise" 😄 Thank you very much, when I first searched I really expected something like this, I just didn't find it.
I now get a
Copy code
Required PromiseResult
Found Promise
Despite the inner function also just returning a
Promise
. Guess I'll tinker some more
Really struggling with the Promise part. I don't know why before it was okay that the return type was just
Promise
and now it's not. (
UseQueryOptions
from TanStackQuery Wrapper)
Copy code
val queryOptions = jso<UseQueryOptions<Array<User>, Error, Array<User>, QueryKey>> {
    queryKey = USERS_QUERY_KEY
    queryFn = { _ ->
        CoroutineScope(CoroutineName("")).promise {
            getUsers(token)
        }
    }
}
Expected:
PromiseResult
Found
Promise
Adding
.then()
and
.catch()
also doesn't help.
Copy code
CoroutineScope(CoroutineName("")).promise {
    getUsers(token)
}
    .then { it }
    .catch { emptyArray<User>() }
The signature of
getUsers(token)
is unchanged aside from adding
suspend
.
Copy code
suspend fun getUsers(token: AuthJWT): Promise<Array<User>>
a
For the suspend variant of such functions, you should not return Promise - only the value you want to be wrapped, and with the promise, scope it will be wrapped in a promise
So, get users could look something like this:
Copy code
suspend fun getUsers(token: String): Array<User> =
    axios.get(…).then { it.json() }.await()
And after that just use it as a regular suspend function
h
I see, that makes sense. Thank you very much for your insight.
While that changes the type of what goes up the function chain, that doesn't resolve the issue with the PromiseResult though. Sorry if I appear dense here, I am still trying to learn the whole JS/TS thing.
I tried adding
.asDeffered().asPromise()
the compiler is not complaining, I wonder if it works.
a
Could you please share the code that gives the compiler error?
h
It seems they target different implementations of
Promise
one is
LegacyPromise
very confusing.
a
@turansky , do you know what is the issue?
h
Some details are corporate, how comprehensive does it have to be? I think to test it out locally you could add TanStackQuery (
kotlin-tanstack-react-query
) and create an object of
UseQueryOptions
and try to add a
queryFn
:
Copy code
jso<UseQueryOptions<Array<User>, Error, Array<User>, QueryKey>> {
        queryKey = USERS_QUERY_KEY
        queryFn = { _ ->
            CoroutineScope(CoroutineName("")).promise {
                getUsers(token)
            }
                .asDeferred()
                .asPromise()
        }
    }
The return value from
CoroutineScope.promise()
targets
org.jetbrains.kotlin:kotlin-stdlib-js:1.9.22
and the one that
asPromise()
targets is from
org.jetbrains.kotlin-wrappers:kotlin-js-js:1.0.0-pre.690
a
Promise from Katlin-wrappers and from kotlin-stdlib have different types. I think @turansky could help you more with kotlin-wrappers
t
js.promise.Promise
expected in typings
AFAIK we also have
promise
extension
More important thing - in your example you have no connection with component context or query client context
h
Yes, I currently just get NPE because of that. Tomorrow I will look to getting a proper CoroutineContext going.
(I hope there is an example in the wrappers or somewhere) 😄
t
We have similar query configuration and it works without
promise
extension :)
h
Uhh can I find in one of the wrapper examples?
h
Ah yes, we had it that way before. It looks like my colleague even used that example. I was exploring the axios interceptors and wanted to implement something similar to this here. And that requires me to make sure the interceptors are done one after the other.
t
For cancellation react query provide signal
h
My intention was to make an interceptor that checks the token for validity and expiration and just fetch the new token.
t
For refresh you can use query.refresh
We skip query.data if data is outdated
h
I briefly tried using signal for cancellation, but wanted to get token refresh done first. For cancellation I tried:
Copy code
val abortController = jso<AbortController>()
and in the interceptor
Copy code
abortController.abort()
but that was me with
abort is not a function
And I was unsure how to implement this JS part:
Copy code
return {
    ...config,
    signal: controller.signal
  };
t
Looks like you duplicate react query functionality
h
Ah you say that recht query itsel provides the signal. Man I have to really delve into that one.
t
It has most required logic
Common scheme 1. write simple requests 2. Use query result for manipulations
h
My overall problem is that my knowledge of react and things like react-query is not yet deep enough to apply it to some of the challenges. And connecting the dots bewteen the react docs and KotlinJS and Coroutine and 3rd party libs 😅
t
Here looks like coroutines are redundant
Axios can have direct connection with react query
Probably even fetchAsync is fine for your cases
😉
h
So in the react query way it would be to cancel the request, refresh the token and start a new request?
From where I stand knowledge wise I don't understand what you mean with Axios can have a direct connection with react query.
t
Direct connection = without coroutines
h
Ah okay, yes we had that before. And then I wanted to use Axios Interceptor to check for Token validity. One problem was I couldn't look into the provided config, all headers were always empty, the other problem was that starting the token refresh inside an interceptor was of course async so it didn't wat for the refresh to finish.
How would I go about the second part, refreshing the token, in the react query way without using Coroutines?
My initial impulse was something like this, imagine behind
refreshToken
is just another axios request:
Copy code
AxiosInterceptor.AxiosRequestInterceptor(
    { config ->
        val token: String? = config.headers["Authorization"] as? String
        println("Security Interceptor Token: $token")
        refreshToken(token)
		    .then {
                config.headers["Authorization"] = it
            }.catch {
                println(it)
            }
        config
    }, { error ->
        println("interceptor 1 request error: $error")
        Promise.reject(error as JsError)
    }
)
But since refreshToken doesn't block the unaltered request goes through. That's the core of my current problem.
t
Copy code
val tokenQuery = useQuery(jso {
   queryFn = {
      if (headers["Authorization"] == null) {
          refreshToken(token)
      } else {
          Promise.resolve()
      }
})

val dataQuery = useQuery(jso {
    enabled = tokenQuery.loaded
    ...
})
h
And that would be inside the
useUsers()
function in the wrapper example?
I think I really have to read up on React-Query.
t
And that would be inside the
useUsers()
function in the wrapper example?
In simple case - yes
If problem is common - custom base hook will be helpful
h
But that would completely omit the axios interceptors, doesn't it? I'd just have a custom interceptor pattern with react-query.
t
> But that would completely omit the axios interceptors, doesn't it? Looks like they don't help in your case :)
h
Okay, then I'll do it with react-query for the refresh. On a last note, do you happen know why I cannot look into the config.headers in the Axios Interceptors? I guess that's more of an Axios problem than yours, but in the Axios TS examples they can look inside. But when I do
Copy code
println(config.headers["Authorization"])
it's always null. I can see the header in the outgoing request, so it's there.
In my wrapper it's set as
ReadonlyRecord<String,String>
t
Looks like cache from previous requests or value from cookie
If multiple interceptors will request new token - it's bad
In React Query token request will be single (by configuration) and you won't have multiple token refresh requests