Bagaimana cara memanggil permintaan asinkron secara sinkron dengan nilai batas waktu?

Jan 07 2021

Saya harus secara sinkron memanggil permintaan api asynchronous. Karena permintaan api ini membutuhkan waktu lama untuk dijawab, saya juga ingin mengatur waktu tunggu untuk gagal dalam permintaan api dan melanjutkan dengan null. Ini kode saya untuk memanggil api ini:

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

Saya tidak bisa mengubah cara External3rdPartyApikerjanya.

Saya pikir kode di atas terlihat jahat. Juga, saya membaca di jawaban lain :

withTimeout { ... }dirancang untuk membatalkan operasi yang sedang berlangsung saat batas waktu, yang hanya mungkin jika operasi yang dimaksud dapat dibatalkan .

Jadi, yang harus saya gunakan suspendCancellableCoroutinebukan suspendCoroutine?

Bagaimana saya bisa menulisnya dengan cara yang lebih baik?

Jawaban

1 ChristianB Jan 07 2021 at 19:42

Penggunaannya suspendCoroutinebaik-baik saja jika Anda tidak dapat (atau tidak ingin) menangani pembatalan Coroutine. Tetapi karena Anda memiliki waktu tunggu, Anda harus mempertimbangkan untuk menggunakan suspendCancellableCoroutine dan menangani peristiwa pembatalan untuk menghentikan pekerjaan (dalam fungsi pihak ketiga - jika Anda bisa).

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

Saat fungsi pihak ketiga Anda melontarkan pengecualian, Anda dapat mencoba menangkapnya dan menyelesaikan kelanjutan Anda menggunakan melanjutkan dengan exceptionatau mengembalikan nullnilai (bergantung pada kasus penggunaan Anda):

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

Ayo kumpulkan semuanya

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