I have to synchronously call an asynchronous api request. Since this api request takes long time to answer, I also want to set a timeout to fail the api-request and continue with null. Here's my code to call this api:
private suspend fun call(
accessStage: AccessStage,
): Response? = withContext<Response?>(Dispatchers.IO) {
return@withContext withTimeoutOrNull(1000) {
suspendCoroutine<Response?> { continuation ->
val request = External3rdPartyApi.newRequest(
accessStage
) { response, throwable ->
continuation.resume(response)
}
request.parameters = hashMapOf<String, String>().apply {
put["token"] = External3rdPartyApi.TOKEN
put["salt"] = External3rdPartyApi.calculateSalt(accessStage)
}
request.executeAsync()
}
}
}
I can't change how External3rdPartyApi works.
I think above code looks evil. Also, I read in another answer:
withTimeout { ... }is designed to cancel the ongoing operation on timeout, which is only possible if the operation in question is cancellable.
So, should I use suspendCancellableCoroutine instead of suspendCoroutine?
How can I write it in a better way?
Using suspendCoroutine is fine if you can't (or do not wanna) handle cancelation of the Coroutine. But because you have a timeout, you should consider using suspendCancellableCoroutine and handle the cancelation event to stop the work (in the third party function - if you can).
suspendCancellableCoroutine<T> { continuation ->
continuation.invokeOnCancellation { throwable ->
// now you could stop your (third party) work
}
}
When your third party function throws an exception, you could try-catch it and finish your continuation using either resuming with the exception or return a null value (depends on your use case):
suspendCancellableCoroutine<T?> { continuation ->
try {
continuation.resume(thirdParty.call())
} catch (e: Exception) {
// resume with exception
continuation.resumeWithException(e)
// or just return null and swallow the exception
continuation.resume(null)
}
}
Lets put all together
suspend fun call(): Response? = withContext(Dispatchers.IO) {
return@withContext withTimeoutOrNull(1000L) {
return@withTimeoutOrNull suspendCancellableCoroutine { continuation ->
try {
continuation.resume(External3rdPartyApi.newRequest(accessStage))
} catch (e: Exception) {
// resume with exception
continuation.resumeWithException(e)
// or just return null and swallow the exception
continuation.resume(null)
}
// in case the coroutine gets cancelled - because of timeout or something else
continuation.invokeOnCancellation {
// stop all the work
}
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With