Come chiamare la richiesta asincrona in modo sincrono con un valore di timeout?
Devo chiamare in modo sincrono una richiesta API asincrona. Poiché questa richiesta API richiede molto tempo per rispondere, desidero anche impostare un timeout per non riuscire la richiesta API e continuare con null. Ecco il mio codice per chiamare questa 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()
}
}
}
Non posso cambiare il modo in cui External3rdPartyApi
funziona.
Penso che il codice sopra sembri malvagio. Inoltre, ho letto in un'altra risposta :
withTimeout { ... }
è progettato per annullare l'operazione in corso per timeout, che è possibile solo se l'operazione in questione è annullabile .
Quindi, dovrei usare al suspendCancellableCoroutine
posto di suspendCoroutine
?
Come posso scriverlo in un modo migliore?
Risposte
L'uso suspendCoroutine
va bene se non puoi (o non vuoi) gestire la cancellazione della Coroutine. Ma poiché hai un timeout, dovresti considerare l'utilizzo di suspendCancellableCoroutine e gestire l'evento di annullamento per interrompere il lavoro (nella funzione di terze parti, se puoi).
suspendCancellableCoroutine<T> { continuation ->
continuation.invokeOnCancellation { throwable ->
// now you could stop your (third party) work
}
}
Quando la tua funzione di terze parti genera un'eccezione, puoi provare a catturarla e terminare la continuazione utilizzando la ripresa con exception
o restituire un null
valore (dipende dal tuo caso d'uso):
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)
}
}
Mettiamo tutto insieme
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
}
}
}
}