Coroutine

[Flow Basics 6] Flow Exceptions

xiaolin219 2024. 1. 30. 23:06

1. 국룰 try/catch

아래 예시처럼 emitter에서 발생한 Excpetion, 그리고 collecter 에서 발생한 Excpetion 둘 다 try/Catch를 통해 잡을 수 있다.

fun exceptionExample(): Flow<String> = flow {
    for (i in 1..3) {
        println("Emitting $i")
        emit(i) // emit next value
    }
}.map { value ->
    check(value <= 1) { "Crashed on $value" } ✅
    "string $value"
}

fun main(): Unit = runBlocking {
    try {
        exceptionExample().collect { value ->
            println(value)
            check(value.contains("1")) { "Collected $value" } ✅
        }
    } catch (e: Throwable) {
        println("Caught $e")
    }
}

2. Exception transparency (함수형으로 catch 사용하기)

위 로직처럼 try/catch로 감싸진 flow{..} 블록 내부에서 데이터를 emit하는 것은 Transparent to Excpetion(예외 투명성) 위반이다. emitter가 catch연산자를 사용한다면 Excpetion Transparency도 지키면서 Exception handling 로직을 캡슐화 할 수 있다는 장점이 있다.

catch 블록 내부를 아래와 같이 처리할 수 있다.

  1. 새로운 Exception을 throw하는 법

  2. 새로운 값 emit하기

  3. 아무것도 안해도 되고, 로깅이나 다른 코드 무엇이든 들어갈 수 있다.

fun simple(): Flow<Int> = flow {
    for (i in 1..3) {
        println("Emitting $i")
        emit(i)
    }
}

fun main() = runBlocking<Unit> {
    simple()
        .catch { e -> println("Caught $e") } // does not catch downstream exceptions
        .collect { value ->
            check(value <= 1) { "Collected $value" }                 
            println(value) 
        }
}

그런데 여기서 주의해야할 점이 있다.

catch 연산자는 중간 연산자라 upStream의 Exception만을 handling한다.
위 코드에서와 같이 catch 이후에 downStream이 위치해있을 경우 Exception은 저항없이 발생하게 된다!^^

이것은 onEach 블록을 collect와 함께 사용함으로써 해결할 수 있다.

collect 블록 안에 들어있던 로직은 onEach로 옮긴다. check와 같이 유효성 검증 함수를 onEach내부에서 검사하자.

onEach가 끝난 이후 catch를 통해서 exception을 핸들링 하고, 그 이후에 최종적으로 collect를 해주면 된다.