05
05

 

์•ˆ๋“œ๋กœ์ด๋“œ ์˜คํ”ˆ์ฑ„ํŒ…๋ฐฉ์—์„œ ๋ณด๊ฒŒ ๋˜์–ด ์ฐพ์•„๋ณธ ์ฃผ์ œ๋‹ค.

CompletableDeferred

์ฝœ๋ฐฑ ๊ธฐ๋ฐ˜์˜ ๋น„๋™๊ธฐ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ๋•Œ ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํด๋ž˜์Šค๋‹ค.

Deferred ํด๋ž˜์Šค ๊ธฐ๋ฐ˜์ด๋ผ ๋ฐ˜ํ™˜ ๊ฐ’์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋จผ์ €๋ณด์ž.

public interface CompletableDeferred<T> : Deferred<T> {
    public fun complete(value: T): Boolean
    public fun completeExceptionally(exception: Throwable): Boolean
}

์ด ๋•Œ ์‚ฌ์šฉ๋˜๋Š” complete๋Š” ์ž์‹ ์ฝ”๋ฃจํ‹ด ๊นŒ์ง€ ์ „ํŒŒ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์—ฌ๋Ÿฌ๊ฐœ์˜ ์ฝ”๋ฃจํ‹ด์œผ๋กœ ์ค‘์ฒฉ๋˜์–ด์žˆ์œผ๋ฉด ๋ชจ๋“  ์ž‘์—…์ด ๋๋‚˜์•ผ complete๊ฐ€ true๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์ž‘์—…์ด ๋๋‚˜์ง€ ์•Š์€ ๊ฒฝ์šฐ๋Š” ์ž‘์—…์ด ๋๋‚  ๋•Œ ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฐ๋‹ค. complete์•ˆ์˜ value๊ฐ’์œผ๋กœ ์™„๋ฃŒ๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค. completeExceptionally๋Š” ์ด๋ฆ„์—์„œ ์•Œ ์ˆ˜ ์žˆ๋“ฏ์ด ์˜ˆ์™ธ๊ฐ€ ํŠธ๋ฆฌ๊ฑฐ๋‹ค.

suspend fun fetchDataFromServer(): Data {
    val deferred = CompletableDeferred<Data>()
    
    someAsyncOperation { data ->
    // ... ์ž‘์—…๋“ค
        deferred.complete(data)
    }
    
    return deferred.await()
}

CompletableDeferredํด๋ž˜์Šค๋กœ ๊ฐ์ฒด๋ฅผ ํ•˜๋‚˜ ๋งŒ๋“ค๊ณ , ๋น„๋™๊ธฐ ์ž‘์—… ๋ธ”๋ก์•ˆ์— ์ด ๋ณ€์ˆ˜๋ฅผ ๋„ฃ์–ด์ฃผ๋ฉด ์ œ์–ดํ•  ์ˆ˜ ์žˆ๊ฒŒ๋œ๋‹ค. ํ•ด๋‹น ๋ธ”๋ก์˜ ์ž‘์—…์ด ๋ชจ๋‘ ๋๋‚˜๋ฉด complete๊ฐ€ ๋ถˆ๋ฆฌ๊ณ , ๊ทธ ์•ˆ์— ๋‹ด๊ธด ๊ฐ’์„ deffered.await()๋กœ ๊บผ๋‚ด ์“ธ ์ˆ˜ ์žˆ๋‹ค.

 

์ด์ „์— ๊ทธ๋ƒฅ ์‚ฌ์šฉํ•˜๋˜ suspendable coroutine(async(์–˜๋„ ๋ฐ˜ํ™˜๊ฐ’์ด Deferred๋ผ์„œ await๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค), delay๊ฐ™์€) ๊ฒƒ ๊ณผ ์ฐจ์ด์ ์ด ๋ฐ”๋กœ ๋ณด์ธ๋‹ค๋ฉด ์ฝ”๋ฃจํ‹ด ๋งˆ์Šคํ„ฐ๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ๊ฒ ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋˜ async ๊ฐ™์€๊ฑด ๋ฐ”๊นฅ์— ์žˆ๋˜ ๋ณ€์ˆ˜๋กœ ๋‚ด๋ถ€ ์ฝ”๋ฃจํ‹ด์„ ์ œ์–ดํ•˜๋Š” ๊ฒƒ์ด ๋ถˆ๊ฐ€๋Šฅํ–ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์ด ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด, ๋น„๋™๊ธฐ ์ž‘์—…์„ ํ•  ์ฝ”๋ฃจํ‹ด ์Šค์ฝ”ํ”„ ๋ฐ”๊นฅ์— ๊ฐ์ฒด๋ฅผ ์„ ์–ธํ•ด์„œ ๊ฐ’์„ ๊ฐ์‹œํ•˜๋ฉฐ ๊ฒฐ๊ณผ๊ฐ’๋„ ๊ฐ€์ ธ์˜จ๋‹ค๋Š” ์ฐจ์ด์ ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

๊ทธ๋Ÿผ ์˜๋ฌธ์ ์ด ํ•˜๋‚˜ ์ƒ๊ธด๋‹ค. async๋นŒ๋”๋ฅผ ์“ฐ๋ฉด ๋” ๊ฐ„๋‹จํ•œ๋ฐ ์™œ ๊ตณ์ด CompletableDeferred๋ฅผ ์จ์•ผํ•˜๋‚˜? async ๊ณ„์† ์จ๋„ ๋œ๋‹ค. ์‚ฌ์šฉ๋ฒ• ๊ฐ„๋‹จํ•˜๊ณ  await๋กœ ๊ฐ’ ๋ฐ›์•„์˜ฌ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ CompletableDeferred๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ˆ˜๋™์œผ๋กœ ๊ฒฐ๊ณผ๋ฅผ ์„ค์ •ํ•ด์„œ ๊ฐ€์ ธ์˜ค๊ธฐ ๋•Œ๋ฌธ์— ์ข€ ๋” ์ •๊ตํ•˜๊ฒŒ ๊ฐ’์„ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ๊ณ , ์ž‘์—…์ด ์ทจ์†Œ๋  ๋•Œ ์ˆ˜ํ–‰ํ•  ๋‚ด์šฉ์„ ๋”์šฑ ์ž์„ธํ•˜๊ฒŒ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๊ฒŒ๋œ๋‹ค.

 

์ข€ ๋” ์˜ˆ์‹œ๋ฅผ ๋“ค์–ด๋ณด๊ฒ ๋‹ค. ์—ฌ๋Ÿฌ๊ฐœ์˜ api ๊ฒฐ๊ณผ๊ฐ’์„ ์กฐํ•ฉํ•ด์„œ ์‚ฌ์šฉํ•ด์•ผ๋œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด์ž.

suspend fun fetchDataFromServer(): String {
    val deferred1 = CompletableDeferred<String>()
    val deferred2 = CompletableDeferred<String>()

    viewModelScope.launch {
        val result1 = fetchData1()
        deferred1.complete(result1)
    }

    viewModelScope.launch {
        val result2 = fetchData2()
        deferred2.complete(result2)
    }

    // ๋‘ ์ž‘์—…์ด ๋ชจ๋‘ ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆผ
    val data1 = deferred1.await()
    val data2 = deferred2.await()

    return "$data1 and $data2"
}

 

data1์„ ๋ฐ›์•„์˜ฌ ๋•Œ๋Š” deferred1์ด ๋๋‚˜์•ผ ๊ฐ’์„ ๊ฐ€์ ธ์˜ค๊ณ , data2์—ญ์‹œ deferred2๊ฐ€ ๋๋‚˜์•ผ ๊ฐ’์„ ๊ฐ€์ ธ์˜จ๋‹ค. ์™„๋ฃŒ๊ฐ€ ๋˜๋Š” ์‹œ์ ์— ์–ด๋–ค ๊ฐ’์„ ๊ฐ€์ ธ์˜ฌ์ง€ ๋ช…์‹œํ•˜๋Š” ๊ฒŒ ๊ฐ€๋Šฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ€๋…์„ฑ์ด ์ข‹์•„์ง€๊ณ , ๊ฐ์ฒด์ด๋ฆ„์„ ์ž‘์—…์ด๋ฆ„์œผ๋กœ ๋ฐ”๊ฟ”์ค€๋‹ค๋ฉด ๋” ์ฝ๊ธฐ ์ข‹๊ฒŒ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๊ฒŒ๋œ๋‹ค.

val deferred1 = viewModelScope.async { fetchData1() }
val deferred2 = viewModelScope.async { fetchData2() }

deferred๋ถ€๋ถ„๋งŒ async๋กœ ๋ฐ”๊พผ ์ฝ”๋“œ๋‹ค. fetchData๊ฐ€ ์ˆ˜ํ–‰๋˜๊ณ ๋‚˜์„œ ์–ด๋–ค ๊ฐ’์ด ๋ฐ˜ํ™˜๋  ์ง€ ์•Œ ์ˆ˜ ์—†๋‹ค. ๋‹จ์ˆœํžˆ ์ด๋ ‡๊ฒŒ๋งŒ ๋†“๊ณ  ๋ด๋„ ์žฅ์ ์ด ๋ณด์ด๋Š”๋ฐ, api๋ฅผ ๋”์šฑ ๋งŽ์ด ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด ์ด ์žฅ์ ์ด ๋” ๋ถ€๊ฐ๋  ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•œ๋‹ค.

suspendCancellableCoroutine

์•ž์„œ ์–˜๊ธฐํ•œ ComletableDeferred์™€ ๊ฑฐ์˜ ๋™์ผํ•œ ๊ฐœ๋…์ด๋‹ค. ์ฐจ์ด์ ์œผ๋กœ๋Š” suspendCancellableCoroutine์ด ์ฝ”๋ฃจํ‹ด ๋นŒ๋”๋ผ๋Š” ๊ฒƒ์ด๋‹ค.

suspend fun <T> suspendCancellableCoroutine(
    block: (CancellableContinuation<T>) -> Unit
): T

์ฝ”๋ฃจํ‹ด ๋ณธ๋ฌธ์„ CancellableContinuation์œผ๋กœ ๋ฐ›๊ธฐ ๋•Œ๋ฌธ์— ์ด ์Šค์ฝ”ํ”„ ์•ˆ์—์„œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ๋•Œ ์ทจ์†Œ์— ๋Œ€ํ•œ ํ–‰๋™์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ž‘์—…์ด ์™„๋ฃŒ๋˜์—ˆ๋‹ค๋ฉด resume, ์ž‘์—…์ค‘ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค๋ฉด resumeWithException์œผ๋กœ ์ œ์–ด๊ฐ€๋Šฅํ•˜๋‹ค. ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด๊ฒƒ ์—ญ์‹œ ์ฝœ๋ฐฑ๊ธฐ๋ฐ˜์˜ ๋น„๋™๊ธฐ์ž‘์—…์— ์ ํ•ฉํ•˜๋‹ค.

suspend fun fetchData(): String {
    return suspendCancellableCoroutine { continuation ->
        val job = CoroutineScope(Dispatchers.IO).launch {
        	try {
            	continuation.resume("User data")
            } catch (e: Exception) {
                continuation.resumeWithException(e)
            }
        }

        continuation.invokeOnCancellation {
            job.cancel()
        }
    }
}

job์œผ๋กœ ์ง€์ •ํ•œ ์ž‘์—…์ด ์™„๋ฃŒ๋˜๋ฉด, continuation์˜ resume์— ๊ฐ’์„ ์ „๋‹ฌํ•ด์„œ fetchData์˜ ๋ฐ˜ํ™˜๊ฐ’์œผ๋กœ ์‚ฌ์šฉํ•˜๊ณ , ์ž‘์—…์ค‘์— ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค๋ฉด resumeWithException์„ ์‚ฌ์šฉํ•ด ์ž‘์—…์ค‘์ด๋˜ ์ฝ”๋ฃจํ‹ด ์™ธ๋ถ€๋กœ ์ „ํŒŒ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ   invokeOnCancellaion ๋ธ”๋ก์—๋Š” ์ทจ์†Œ๊ฐ€ ๋ฐœ์ƒํ•  ๋•Œ ์ˆ˜ํ–‰ํ•  ์ž‘์—…์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค. 

resumeWithException์€ ์˜ˆ์™ธ๋ฅผ ์ „ํŒŒ์‹œํ‚ค๊ธฐ ๋•Œ๋ฌธ์— ์˜ˆ์‹œ์ฝ”๋“œ ๊ธฐ์ค€์œผ๋กœ fetchData๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ์ชฝ์—์„œ ์˜ˆ์™ธ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์ค˜์•ผํ•˜๊ณ , invokeOnCancellation์€ ์™ธ๋ถ€์—์„œ suspendCancellableCoroutine์— ๋Œ€ํ•œ ์ทจ์†Œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ์ด์ „ ์ž…๋ ฅ๋œ ๊ฐ’์„ ๋‚ ๋ฆฐ๋‹ค๋˜๊ฐ€ ํ•˜๋Š” ์ดˆ๊ธฐํ™” ๋กœ์ง์„ ๋„ฃ์–ด๋‘๋ฉด ๋  ๊ฒƒ ๊ฐ™๋‹ค. 

 

์ด๋•Œ Cancellable์˜ ์˜๋ฏธ๊ฐ€ ์ค‘์š”ํ•˜๋‹ค. ์ด๋ฆ„์— ๋“ค์–ด์žˆ๋Š” Cancellable์€ ์ด ์ฝ”๋ฃจํ‹ด ์Šค์ฝ”ํ”„๋ฅผ ์™ธ๋ถ€์—์„œ ์ทจ์†Œ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋Š” ์ง€์— ๋Œ€ํ•œ ์—ฌ๋ถ€๋ฅผ ์˜๋ฏธํ•œ๋‹ค. ์ฆ‰ ์™ธ๋ถ€์—์„œ ์˜ˆ์™ธ๋ฅผ ๋ฐ›๊ณ  -> ์ทจ์†Œ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด invokeOnCancellation ๋‚ด๋ถ€์— ์ž‘์„ฑํ•ด๋‘” ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋œ๋‹ค.

 

try-catch ๋Œ€์‹  runCatching์„ ์‚ฌ์šฉํ•ด ์ข€ ๋” ์ฝ”ํ‹€๋ฆฐ์Šค๋Ÿฝ๊ฒŒ ๋ฐ”๊ฟ”๋ณด๋ฉด 

suspend fun fetchData(): String {
    return suspendCancellableCoroutine { continuation ->
        val job = CoroutineScope(Dispatchers.IO).launch {
            val result = runCatching {
                "User data"
            }

            result.onSuccess { data ->
                continuation.resume(data)
            }

            result.onFailure { e -> // Throwable์ด ๋“ค์–ด์˜จ๋‹ค.
                continuation.resumeWithException(e)
            }
        }

        continuation.invokeOnCancellation {
            job.cancel()
        }
    }
}

์ด๋ ‡๊ฒŒ ๊ฐ€๋…์„ฑ ์ข‹๊ฒŒ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๋‹ค.

onSuccess๋ฅผ ์“ฐ์ง€์•Š๊ณ  ๊ทธ๋ƒฅ ๋ฆฌ์‹œ๋ฒ„๋กœ api๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ๋Š”๋ฐ, ๊ทธ๋ ‡๊ฒŒ ํ•˜๋ฉด ์„ฑ๊ณตํ–ˆ์„ ๋•Œ ๋ฐ์ดํ„ฐ๋ฅผ this๋กœ๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค. ๋‚˜๋Š” ๋ฐ›์•„์˜ฌ ๊ฐ์ฒด ๋ฐ์ดํ„ฐ์—๋‹ค๊ฐ€ ์ด๋ฆ„์„ ์ง€์ •ํ•˜๋Š” ๊ฒŒ ๊ฐ€๋…์„ฑ์ด ์ข‹๋‹ค๊ณ  ์ƒ๊ฐํ•ด์„œ onSuccess๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฑธ ์„ ํ˜ธํ•œ๋‹ค. 

๋„์›€์ด ๋๋‹ค๋ฉด ๋Œ“๊ธ€์ด๋‚˜ ๊ณต๊ฐ ๋ฒ„ํŠผ ํ•œ ๋ฒˆ์”ฉ ๋ˆ„๋ฅด๊ณ  ๊ฐ€์ฃผ์„ธ์š”!

 

๋ฐ˜์‘ํ˜•
COMMENT