봄날은 갔다. 이제 그 정신으로 공부하자

10. Reactive - 오류 처리 연산자 본문

Reactive

10. Reactive - 오류 처리 연산자

길재의 그 정신으로 공부하자 2021. 6. 25. 11:11

이번 글에서는 오류 처리 연산자에 대해 설명하도록 하겠습니다.

엡 개발 과정에서 오류는 언제든 발생할 수 있지만 개발자는 사용자가 이를 인지하지 못하고 자연스럽게 동작할 수 있도록 오류를 적절히 관리해야 할 필요가 있습니다.

오류 처리 연산자는 Observable이 발생 시킨 오류를 복구할 수 있도록 도와주는 연산자입니다.

오류 처리 연산자는 오류 발생 시 이벤트를 캐치해서 이후 처리 방안을 지정해주는 “onErrorXXX 계열 연산자”와 

오류 발생 시 원천 Observable의 재시도를 지정하는 “retryXXX 계열 연산자”로 구분할 수 있습니다.

   - onErrorXXX 계열 연산자

   - retryXXX 계열 연산자

 

onErrorReturn

onErrorReturn 연산자는 Observable에서 아이템 방출 처리 중 에러가 발생했을 때 구독자에게 대체로 방출할 수 있는 기본 값을 지정할 수 있도록 지원하는 연산자 입니다.

사용 방법은 아래와 같습니다.

fun funExam(){
    Observable
            .just(1,2,3,4,5)
            .map{
                it/(3-it)
            }.onErrorReturn { 
                -1
            }.subscribe {
                println("Next: $it")
            }
}

 

위 코드는 방출하려는 아이템의 값이 3인 경우 map 연산자에서 오류가 발생하도록 되어 강제되어 있는 코드 입니다.

오류 발생 시 onErrorReturn에서 오류를 캐치하여 -1을 방출해주도록 되어 있기 때문에 아래와 같이 출력 됩니다.

Next: 0
Next: 2
Next: -1

 

onErrorResumeItem

onErrorResumeItem() 연산자는 onErrorReturn 연산자와 동일한 기능을 수행하는 연산자입니다. onErrorResumeItem 연산자 정의를 보면 아래와 같이 내부에서 onErrorReturn 연산자를 호출한 다는 것을 알 수 있습니다.

@CheckReturnValue
@SchedulerSupport(SchedulerSupport.NONE)
@NonNull
public final Observable<T> onErrorReturnItem(@NonNull T item) {
    Objects.requireNonNull(item, "item is null");
    return onErrorReturn(Functions.justFunction(item));
}

 

두 연산자의 차이점은 오류 발생 시 onErrorReturn 연산자는 오류 이벤트를 전달 받을 수 있는 반면 onErrorResumeItem 연산자는 오류 이벤트를 전받 받지 못하고 대체로 방출할 아이템만 정의할 수 있다는 차이입니다.

fun funExam(){
    Observable
        .just(1,2,3,4,5)
        .map{
            it/(3-it)
        }.onErrorReturnItem(3)
        .subscribe {
            println("Next: $it")
        }
}

 

출력 결과는 onErrorReturn 연산자와 동일합니다.

 

onErrorResumeWith

onErrorResumeWith() 연산자는 오류 발생 시 이벤트로 구독자 넘겨받아 구독자에게 다음 이벤트를 직접적으로 전달하는 오류 처리 연산자 입니다.

fun funExam(){
    Observable
        .just(1,2,3,4,5)
        .map{
            it/(3-it)
        }.onErrorResumeWith { observer->
            observer.onNext(3)
        }.subscribe {
            println("Next: $it")
        }
}

 

위 코드를 보면 오류 발생 시 onErrorResumeWith() 연산자 파라미터로 구독자가 넘어오는 것을 확인할 수 있습니다.

이 연산를 사용해 onNext, onError, onComplete를 직접적으로 호출 할 수 있습니다.

위 예제 코드에서는 onNext(3)을 호출 했으므로 아래와 같은 출력 결과를 가집니다.

Next: 0
Next: 2
Next: 3

 

onErrorResumeNext

오류 발생 시 아이템 방출이 중단되는게 아니라 다른 Observable로 방출을 전환할 필요가 있는 경우도 있는데 이때 유용한 연산자가 onErrorResumeNext 연산자입니다.

onErrorResumeNext 연산자는 에러가 발생했을 시에 다른 Observable을 구독할 수 있도록 합니다.

fun funExam(){
    Observable
            .just(1,2,3,4,5)
            .map{
                it/(3-it)
            }.onErrorResumeNext { 
                Observable.range(10, 5)   
            }.subscribe {
                println("Next: $it")
            }
}

 

위 코드의 경우 세 번째 방출에서 에서 오류가 발생하고 onErrorResumeNext 연산자가 이를 캐치한 후 Observable.range(10, 5)이 아이템 방출을 시작합니다.

출력 결과는 아래와 같습니다.

Next: 0
Next: 2
Next: 10
Next: 11
Next: 12
Next: 13
Next: 14

 

onErrorComplete

onErrorComplete() 연산자는 오류 발생 시 개발자가 오류 상태를 확인하고 논리 표현식 결과에 따라 complete 이벤트를 보낼 지 error 이벤트를 보낼지 선택할 수 있도록 지원하는 연산자 입니다.

사용 방법은 다음과 같습니다.

fun funExam(){
    Observable
        .just(1,2,3,4,5)
        .map{
            it/(3-it)
        }.onErrorComplete {
            true
        }.subscribeBy(onNext = {
            println("Next: $it")
        },onError = {
            println("Error: $it")
        },onComplete = {
            println("Completed")
        })
}

 

위 코드에서는 오류 발생 시 onErrorComplete 연산자에서 논리 표현식이 참(true)를 리턴하도록 되어 있으므로 아래와 같은 결과를 출력합니다.

Next: 0
Next: 2
Completed

 

하지만 onErrorComplete 연산자에서 논리 표현식이 거짓(false)를 리턴한다면 error 이벤트가 전달되므로 아래와 같은 결과를 출력합니다.

Next: 0
Next: 2
Error: java.lang.ArithmeticException: divide by zero

 

retry

retry() 연산자는 오류가 발생했을 때 동일한 Observable에 연산을 재시도하거나 다시 구독할 수 있도록 지원해주는 오류 처리 연산자 입니다. retry 연산자는 논리 표현식이 참을 반환하는한 계속 재시도하고 거짓을 반환하면 바로 구독자에게 오류를 전달합니다.

fun funExam(){
    Observable
            .just(1,2,3,4,5)
            .map {
                it/(3-it)
            }.retry(3)
            .subscribeBy (
                onNext = {
                    println("Next: $it")
                }, onError = {
                    println("Error")
                })

 

위 코드는 오류 발생 시 retry(count: 3)을 사용했으므로 오류 발생 시 3회를 재시도하는 코드이므로 출력 결과는 아래와 같습니다.

Next: 0
Next: 2
Next: 0
Next: 2
Next: 0
Next: 2
Next: 0
Next: 2
Error

 

retryUntil

retryUntil() 연산자는 논리 표현식이 참이면 재시도를 중단하는 연산자로 사용 방법은 아래와 같습니다.

fun funExam(){
    var retryCount = 0
    Observable
        .just(1,2,3,4,5)
        .map {
            it/(3-it)
        }.retryUntil {
            (++retryCount == 2)
        }.subscribeBy (
            onNext = {
                println("Next: $it")
            }, onError = {
                println("Error")
            })
}

 

위 코드를 보면 retryUntil 연산자에서 오류 발생 시 retryCount가 2인 경우 true를 리턴하므로 2가 아닌 경우 계속 재시도를 하므로 아래와 같이 결과가 출력됩니다.

Next: 0
Next: 2
Error

 

retryWhen

retryWhen() 연산자는 오류 처리 연산자 중 제일 복잡한 연산자로 오류 발생 시 정해진 시간 이후에 정해진 횟수만큼 재시도를 수행하는 연산자 입니다.

 

아래 예제 코드의 retryWhen 현산자의 인자인 errors는 Observable입니다. 이 상태에서 재시도할 때는 Observable.range(1,3)과 zip() 함수로 두 Observable을 합성합니다. 

즉 , 3번의 재시도를 하게됩니다. 그리고 재시도할 때 매번 timer() 함수를 호출하여 retryCount * 1000ms씩 대기 시간을 추가하므로 처음에는 1sec 후에 재시도를 시도하고 이후부터는 2sec 마지막 세번째 재시도는 3sec 후에 하게됩니다. 

fun funExam(){
    Observable
        .just(1,2,3,4,5)
        .map {
            it/(3-it)
        }.retryWhen {
            it.zipWith(Observable.range(1, 3), { _: Throwable, i: Int -> i})
                .flatMap { retryCount: Int ->
                    println("retryCount: $retryCount")
                    Observable.timer((1000*retryCount).toLong(), TimeUnit.MILLISECONDS)
                }
        }.subscribeBy (
            onNext = {
                println("Next: $it")
            }, onError = {
                println("Error")
            })
}

 

출력 결과는 다음과 같습니다.

16:19:30.565 Next: 0
16:19:30.565 Next: 2
16:19:30.566 retryCount: 1
16:19:31.589 Next: 0
16:19:31.589 Next: 2
16:19:31.590 retryCount: 2
16:19:33.593 Next: 0
16:19:33.593 Next: 2
16:19:33.594 retryCount: 3
16:19:36.597 Next: 0
16:19:36.597 Next: 2

 

정리

이번 글에서는 오류 발생 시 대응할 수 있는 여러가지 오류 처리 연산자에 대해 설명하였습니다.

시작 글에서 언급한 내용을 다시 것 처럼 오류 처리 연산자는 아래와 같이 두가지 계열로 구분할 수 있습니다.

   - onErrorXXX 계열: 오류 발생 시 이벤트를 캐치해서 이후 처리 방안을 지정해주는 연산자

   - retryXXX 계열: 오류 발생 시 원천 Observable의 재시도를 지정하는 연산자

 

오류 처리 연산자 세부 내용은 다시 한번 정리하면 아래 표와 같습니다.

 

다음 글에서는 연산자 종류의 마지막인유틸리티 연산자 대해 설명하도록 하겠습니다.

'Reactive' 카테고리의 다른 글

11. Reactive - 유틸리티 연산자  (0) 2021.06.29
9. Reactive - 조건 연산자  (0) 2021.06.22
8. Reactive - 결합 연산자  (0) 2021.06.18
7. Reactive - 필터 연산자  (0) 2021.06.16
6. Reactive - 변환 연산자  (0) 2021.06.14
Comments