Flow 예외 처리
Flow 수집은 방출하는 곳 혹은 연산자 안의 코드가 예외를 발생시키는 경우 예외와 함께 완료될 수 있다. 예외들을 처리할 수 있는 몇가지 방법이 있다.
수집기에서의 try와 catch
수집기는 예외를 처리하기 위해 Kotlin의 try/catch 블록을 사용할 수 있다 :
fun simple(): Flow<Int> = flow {
for (i in 1..3) {
println("Emitting $i")
emit(i) // emit next value
}
}
fun main() = runBlocking<Unit> {
try {
simple().collect { value ->
println(value)
check(value <= 1) { "Collected $value" }
}
} catch (e: Throwable) {
println("Caught $e")
}
}
📌 전체 코드는 이곳에서 확인할 수 있습니다.
이 코드는 collect 터미널 연산자 안에서 성공적으로 예외를 잡아내며, 그 뒤로 더이상 값이 방출되지 않는다 :
Emitting 1
1
Emitting 2
2
Caught java.lang.IllegalStateException: Collected 2
모든 것이 잡힌다
이전 예제는 방출기나 중간 혹은 터미널 연산자 안에서의 예외들을 모두 잡아낸다. 예를 들어, 방출된 값들이 문자열로 매핑되도록 코드를 바꿔도 코드는 예외를 발생시킨다.
fun simple(): 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() = runBlocking<Unit> {
try {
simple().collect { value -> println(value) }
} catch (e: Throwable) {
println("Caught $e")
}
}
📌 전체 코드는 이곳에서 확인할 수 있습니다.
예외는 여전히 잡혀서 수집을 중단시킨다.*1
Emitting 1
string 1
Emitting 2
Caught java.lang.IllegalStateException: Crashed on 2
📖 아래 내용은 독자의 이해를 위해 번역자가 추가한 글입니다.
*1. 예측하지 못한 곳에서 예외가 나버리면 원하는 결과를 얻기가 어렵기 때문에 문제가 된다.
이 글은 Coroutines 공식 문서를 번역한 글입니다.
원문 : Asynchronous Flow - Flow exceptions
원문 최종 수정 : 2022년 9월 28일
예외 투명성(Exception Transparency)
그러면 어떻게 방출기의 코드가 예외 처리 동작을 캡슐화 할 수 있을까?
Flow는 예외에 투명해야 하고, try/catch 블록 내부에서 flow { ... } 빌더의 값을 방출하는 것은 예외 투명성을 위반하는 것이다. 이렇게 하면 예외를 발생시키는 수집기 이전 예제와 같이 언제나 try/catch를 사용해 예외를 잡아낼 수 있다.
방출기는 catch 연산자를 사용해 예외 투명성을 유지시키고 예외 처리를 캡슐화 할 수 있다. catch 연산자의 body는 예외를 분석하고, 잡은 예외에 따라 다른 방식으로 대응할 수 있다.
- 예외는 throw를 사용해 다시 throw 될 수 있다.
- catch의 body에서 emit을 사용해 값을 방출함으로써, 예외를 방출로 바꿀 수 있다.
- 예외는 무시되거나, 로깅되거나, 다른 코드로 처리될 수 있다.
예를 들어, 예외를 잡는 부분에서 텍스트를 방출하도록 해보자 : *1
simple()
.catch { e -> emit("Caught $e") } // emit on exception
.collect { value -> println(value) }
📌 전체 코드는 이곳에서 확인할 수 있습니다.
투명한 catch
예외 투명도를 존중하는 catch 중간 연산자는 업스트림 예외만을 잡아낸다(catch 윗 부분의 연산자들에서의 예외만을 잡아내고 아래 부분의 예외는 잡아내지 않는다). 만약 collect { ... } 내부의 블록(catch 아래 부분의 코드)이 예외를 발생시키면 예외를 잡아내지 않는다.
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 연산자가 있음에도 "Caught ..." 메세지가 출력되지 않는다.
Emitting 1
1
Emitting 2
Exception in thread "main" java.lang.IllegalStateException: Collected 2
at ...
선언적으로 잡기
collect 연산자의 body를 onEach 내부로 이동하고 catch 연산자를 그 이후에 위치 시킴으로써, catch 연산자의 선언적인 성질을 모든 예외들을 처리하기 위한 욕구와 결합할 수 있다. collect()를 파라미터 없이 사용함으로써 Flow의 수집을 발생시킬 수 있다.
simple()
.onEach { value ->
check(value <= 1) { "Collected $value" }
println(value)
}
.catch { e -> println("Caught $e") }
.collect()
📌 전체 코드는 이곳에서 확인할 수 있습니다.
이제 "Caught ..." 메세지가 출력되는 것을 확인할 수 있고, 명시적으로 try/catch 블록을 사용하지 않고도 모든 예외들을 잡아낼 수 있다.
Emitting 1
1
Emitting 2
Caught java.lang.IllegalStateException: Collected 2
📖 아래 내용은 독자의 이해를 위해 번역자가 추가한 글입니다.
*1. 이 코드의 출력은 다음과 같다.
Emitting 1
string 1
Emitting 2
Caught java.lang.IllegalStateException: Crashed on 2
2가 방출되면 check(value <= 1)에 의해 illegalStateException이 발생하지만, catch 블록에서 이 예외가 잡아진 후 "Caught java.lang.IllegalStateException: Crashed on 2"로 다시 방출된다.
이 글은 Coroutines 공식 문서를 번역한 글입니다.
원문 : Asynchronous Flow - Exception transparency
원문 최종 수정 : 2022년 9월 28일
목차로 돌아가기