จะเรียกคำขอแบบอะซิงโครนัสพร้อมกับค่าการหมดเวลาได้อย่างไร

Jan 07 2021

ฉันต้องเรียกคำขอ API แบบอะซิงโครนัสแบบซิงโครนัส เนื่องจากคำขอ api นี้ใช้เวลาตอบนานฉันจึงต้องการตั้งค่าการหมดเวลาเพื่อล้มเหลวในการร้องขอ api และดำเนินการต่อด้วย null นี่คือรหัสของฉันที่จะเรียก 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()
        }
    }
}

ฉันไม่สามารถเปลี่ยนวิธีการExternal3rdPartyApiทำงานได้

ฉันคิดว่าโค้ดข้างบนดูชั่วร้าย นอกจากนี้ฉันอ่านในคำตอบอื่น :

withTimeout { ... }ได้รับการออกแบบมาเพื่อยกเลิกการดำเนินการที่กำลังดำเนินอยู่เมื่อหมดเวลาซึ่งจะทำได้ก็ต่อเมื่อการดำเนินการที่เป็นปัญหานั้นสามารถยกเลิกได้

ดังนั้นฉันควรใช้suspendCancellableCoroutineแทนsuspendCoroutine?

ฉันจะเขียนให้ดีขึ้นได้อย่างไร

คำตอบ

1 ChristianB Jan 07 2021 at 19:42

การใช้งานsuspendCoroutineทำได้ดีหากคุณไม่สามารถ (หรือไม่ต้องการ) จัดการกับการยกเลิก Coroutine แต่เนื่องจากคุณหมดเวลาคุณควรพิจารณาใช้suspendCancellableCoroutineและจัดการเหตุการณ์การยกเลิกเพื่อหยุดการทำงาน (ในฟังก์ชันของบุคคลที่สาม - ถ้าคุณทำได้)

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

เมื่อฟังก์ชั่นบุคคลที่สามของคุณเกิดข้อยกเว้นคุณสามารถลองจับมันและดำเนินการต่อให้เสร็จโดยใช้การเริ่มต้นใหม่ด้วยค่าexceptionหรือส่งคืนnullค่า (ขึ้นอยู่กับกรณีการใช้งานของคุณ):

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

มารวมกัน

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