(Kotlin Coroutines) 012. Flow completion

Kotlin Coroutines - Flow completion

플로우의 수집은 정상적으로 종료되든 예외가 발생되어서 중지되든 언젠가 종료가 된다.

따라서 우리는 플로우의 종료 시점에 후처리를 진행해야할 필요가 있다.

1. 명령형(Imperative) finally block

플로우를 try-catch로 감쌀 수 있듯이 finally 블록도 추가할 수 있다.

즉, 플로우가 종료된 후 실행할 동작을 finally 블록에 정의할 수 있다는 뜻이다.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun simple(): Flow<Int> = (1..3).asFlow()

fun main() = runBlocking<Unit> {
    try {
        simple().collect { value -> println(value) }
    } finally {
        println("Done")
    }
}  

출력 결과

1
2
3
4
1
2
3
Done

플로우가 종료된 이후 “Done”이 출력된 것을 볼 수 있다.

확실하지만 매우 전통적인 방법이라 할 수 있겠다.

2. Declarative handling

onCompletion 연산자를 선언하여 플로우의 종료 후 처리 동작을 정의할 수 있다.

1의 예제와 동일한 결과를 출력하도록 onCompletion 연산자를 적용하면 아래와 같다.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun simple(): Flow<Int> = (1..3).asFlow()

fun main() = runBlocking<Unit> {
    simple()
        .onCompletion { println("Done") }
        .collect { value -> println(value) }
}

출력 결과

1
2
3
4
1
2
3
Done

3. The key advantage of onCompletion

try-catch-finally와 결과가 똑같다면 onCompletion을 사용함으로써 얻을 수 있는 것이 무엇일까?

onCompletion을 사용하면 Throwable 파라미터를 통해 플로우 수집이 정상 종료되었는지, 예외가 발생되었는지를 알 수 있다.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun simple(): Flow<Int> = flow {
    emit(1)
    throw RuntimeException()
}

fun main() = runBlocking<Unit> {
    simple()
        .onCompletion { cause -> if (cause != null) println("Flow completed exceptionally") }
        .catch { cause -> println("Caught exception") }
        .collect { value -> println(value) }
}

출력 결과

1
2
3
1
Flow completed exceptionally
Caught exception