- 불필요한 코드나 잘못 작성된 내용에 대한 지적은 언제나 환영합니다. 👍
- Kotlin Coroutine 공식 문서 내의 Asynchronous Flow 페이지를 공부하며 이해하기 쉽도록 한글 문맥으로 더 자연스럽게 번역 및 정리한 글입니다.
Flow Exceptions
Flow 수집은 발행자(Emitter) 또는 연산자 내부의 코드에서 Exception이 throw 될 때, Exception 발생과 함께 완료됩니다. 이러한 Exception을 처리하는 방법에는 여러 가지가 있습니다.
Collector try and catch
콜렉터는 try/catch 블록을 사용하여 exception을 처리할 수 있습니다.
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 연산자에서 성공적으로 exception을 catch 하여 처리하고, 그 이후에는 더 이상 플로우에서 값이 발행(emit)되지 않음을 확인할 수 있습니다.
Emitting 1
1
Emitting 2
2
Caught java.lang.IllegalStateException: Collected 2
Everything is caught
이전 예제에서는 발행자(Emitter) 또는 intermediate operator, terminal operatior 내부에서 발생하는 모든 예외를 캐치했습니다. 다음 예제는 발행된 값을 String으로 맵핑하는데, 이 과정에서 코드가 예외를 발생시킵니다.
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")
}
}
실행 결과 마찬가지로 예외가 캐치되고 수집이 중단됩니다.
Emitting 1
string 1
Emitting 2
Caught java.lang.IllegalStateException: Crashed on 2
Exception transparency (예외 투명성)
하지만 발행자(Emitter) 코드가 예외 처리 동작을 캡슐화하는 방법은 무엇일까요?
Flow는 예외에 대해 투명해야 합니다. try/catch 블록 내부 flow { ... } 빌더에서 값을 발행하는 것은 예외 투명성 위반이 됩니다. 이는 이전 예제에서와 같이 콜렉터가 예외를 throw 할 때 항상 try/catch로 catch 할 수 있도록 보장합니다.
발행자(Emitter)는 예외 처리를 캡슐화하는 방법으로 catch 연산자를 사용할 수 있습니다. catch 연산자의 본문은 예외를 분석하고 캐치된 예외가 무엇이냐에 따라 다른 방식으로 대응할 수 있습니다. 이렇게 하면 예외 투명성을 유지하면서 예외 처리를 캡슐화할 수 있습니다.
- throw를 통하여 Exception은 재발생(rethrow)될 수 있습니다.
- catch 블록에서 emit을 사용하여 값 발행으로 변환할 수 있습니다.
- Exception을 무시하거나, 로깅하거나, 다른 코드에서 처리할 수 있습니다.
예를 들어, 다음과 같이 Exception을 catch 했을 때 발행할 텍스트를 설정할 수 있습니다.
simple()
.catch { e -> emit("Caught $e") } // emit on exception
.collect { value -> println(value) }
예시 코드의 출력 결과는 try/catch 블록이 더 이상 존재하지 않아도 이전과 동일합니다.
Emitting 1
string 1
Emitting 2
Caught java.lang.IllegalStateException: Crashed on 2
Transparent catch
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..." 메시지가 인쇄되지 않으며, Exception이 발생합니다.
Emitting 1
1
Emitting 2
Exception in thread "main" java.lang.IllegalStateException: Collected 2 at ...
Catching declaratively (선언적 캐칭)
예외를 처리하는 catch 연산자의 선언적 성격과 모든 예외를 처리하는 방법을 결합할 수 있습니다. collect 연산자의 본문을 onEach로 이동시키고 catch 연산자 앞에 놓습니다. 이렇게 하면 flow의 수집을 매개변수 없이 collect를 호출하여 실행할 수 있습니다.
simple()
.onEach { value ->
check(value <= 1) { "Collected $value" }
println(value)
}
.catch { e -> println("Caught $e") }
.collect()
실행 결과 "Caught..." 메시지가 출력되므로 try/catch 블록을 명시적으로 사용하지 않고도 모든 예외를 catch 한 것을 볼 수 있습니다.
Emitting 1
1
Emitting 2
Caught java.lang.IllegalStateException: Collected 2
[Kotlin Coroutine] Flow 공식 문서 번역 및 정리 (5) - 마지막 편 에서 계속됩니다.
'Kotlin > Coroutines' 카테고리의 다른 글
[Kotlin Coroutine] Flow 공식 문서 번역 및 정리 (5) (1) | 2023.05.04 |
---|---|
[Kotlin Coroutine] Flow 공식 문서 번역 및 정리 (3) (0) | 2023.04.26 |
[Kotlin Coroutine] Flow 공식 문서 번역 및 정리 (2) (0) | 2023.04.23 |
[Kotlin Coroutine] Flow 공식 문서 번역 및 정리 (1) (0) | 2023.04.23 |