Jak wywołać żądanie asynchroniczne synchronicznie z wartością limitu czasu?
Muszę synchronicznie wywołać asynchroniczne żądanie API. Ponieważ odpowiedź na to żądanie interfejsu API zajmuje dużo czasu, chcę również ustawić limit czasu na niepowodzenie żądania interfejsu API i kontynuować z wartością null. Oto mój kod wywołujący ten interfejs 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()
}
}
}
Nie mogę zmienić tego, jak External3rdPartyApi
działa.
Myślę, że powyższy kod wygląda źle. Przeczytałem też w innej odpowiedzi :
withTimeout { ... }
jest przeznaczony do anulowania trwającej operacji po przekroczeniu limitu czasu, co jest możliwe tylko wtedy, gdy daną operację można anulować .
Więc powinienem używać suspendCancellableCoroutine
zamiast suspendCoroutine
?
Jak mogę to lepiej napisać?
Odpowiedzi
Używanie suspendCoroutine
jest w porządku, jeśli nie możesz (lub nie chcesz) obsługiwać anulowania Coroutine. Ale ponieważ masz limit czasu, powinieneś rozważyć użycie suspendCancellableCoroutine i obsłużyć zdarzenie anulowania, aby zatrzymać pracę (w funkcji strony trzeciej - jeśli możesz).
suspendCancellableCoroutine<T> { continuation ->
continuation.invokeOnCancellation { throwable ->
// now you could stop your (third party) work
}
}
Gdy funkcja innej firmy zgłasza wyjątek, możesz spróbować go przechwycić i zakończyć kontynuację, używając wznawiania exception
lub zwracania null
wartości (w zależności od przypadku użycia):
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)
}
}
Połączmy wszystko razem
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
}
}
}
}