Comment appeler une demande asynchrone de manière synchrone avec une valeur de délai d'expiration?

Jan 07 2021

Je dois appeler de manière synchrone une demande d'API asynchrone. Étant donné que cette demande d'API prend beaucoup de temps à répondre, je souhaite également définir un délai d'expiration pour échouer la demande d'API et continuer avec null. Voici mon code pour appeler cette 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()
        }
    }
}

Je ne peux pas changer comment External3rdPartyApifonctionne.

Je pense que le code ci-dessus a l'air maléfique. Aussi, j'ai lu dans une autre réponse :

withTimeout { ... }est conçu pour annuler l'opération en cours sur timeout, ce qui n'est possible que si l'opération en question est annulable .

Alors, devrais-je utiliser à la suspendCancellableCoroutineplace de suspendCoroutine?

Comment puis-je mieux l'écrire?

Réponses

1 ChristianB Jan 07 2021 at 19:42

L'utilisation suspendCoroutineest très bien si vous ne pouvez pas (ou ne voulez pas) gérer l'annulation de la Coroutine. Mais comme vous avez un délai d'expiration, vous devriez envisager d'utiliser suspendCancellableCoroutine et gérer l'événement d'annulation pour arrêter le travail (dans la fonction tierce - si vous le pouvez).

suspendCancellableCoroutine<T> { continuation ->
  continuation.invokeOnCancellation { throwable ->
    // now you could stop your (third party) work  
  }        
}

Lorsque votre fonction tierce lève une exception, vous pouvez essayer de l'attraper et terminer votre continuation en utilisant soit la reprise avec le exceptionsoit le retour d'une nullvaleur (dépend de votre cas d'utilisation):

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)
  }   
}

Permet de tout mettre ensemble

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
      }
    }
  }
}