쾌락코딩

Flow Completion

|

flow가 방출한 모든 값을 정상적으로 다 수집했기 때문에 flow가 종료되었든, 또는 에러가 발생해서 flow가 종료되었든 종료 이후에 어떤 액션을 취하고 싶을 수가 있습니다. 어떤 이유에서든 flow가 끝났을 때 액션을 취하기 위해 명령형, 또는 선언형으로 코드를 작성할 수 있습니다.

Imperative finally block

flow collection이 끝났을 때 어떤 행동을 하고자 한다면, try/catch 를 사용하여 명령형으로 코드를 작성 할 수 있습니다.

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

fun main() = runBlocking<Unit> {
    try {
        foo().collect { value -> println(value) }
    } finally {
        println("Done")
    }
}
// 결과
1
2
3
Done

Declarative handling

선언형으로 코드를 작성하고 싶다면, onCompletion 중간 연산자를 사용하세요. onCompletion 연산자는 colletion이 끝나고 나면 실행됩니다.

foo()
    .onCompletion { println("Done") }
    .collect { value -> println(value) }

onCompletion의 장점은 선언형이라는 점에서 그치지 않습니다. 가장 큰 장점은 onCompletion 이 받는 람다의 nullable한 Throwable 파라미터 입니다. 즉 onCompletion 이 받는 람다의 파라미터(Throwable)가 null이면 collection이 완전하게 수행되었음을 알 수 있고, null이라면 exception으로 인해 flow가 completion 되었음을 알 수 있습니다. 아래에서 foo() flow가 처음 1을 방출하고 에러를 발생시키는 코드를 볼 수 있습니다.

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

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

onCompletioncatch 와는 다르게 exception 을 처리하지는 않습니다. 바로 위의 예제에서 볼 수 있듯이 exceptiononCompletion 이후 계속해서 downstream으로 흐릅니다. 그렇기 때문에 에러 처리는 catch에서 합니다.

Successful completion

onCompletioncatch와 다른 점은, onCompletion은 항상 모든 예외를 받는다는 점입니다. 성공적으로 collection이 완료 되었더라도 null을 수신하고 코드가 실행되는 반면, catch는 전혀 실행되지 않습니다.

Comments