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 블록 내부를 아래와 같이 처리할 수 있다.
새로운 Exception을 throw하는 법
새로운 값 emit하기
아무것도 안해도 되고, 로깅이나 다른 코드 무엇이든 들어갈 수 있다.
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를 해주면 된다.