# catchError, catchErrorJustReturn

옵저버블 처리 과정에서 에러가 발생한 경우 이에 대한 처리가 필요하다. RxSwift에서는 catchError라는 연산자를 통해 에러 발생시 디폴트값을 사용하거나 새로운 옵저버블로 대체하는 방식을 사용한다. next이벤트나 completed이벤트는 구독자에게 그대로 전달한다.

혹은 retry를 통해 에러 발생이 없을때까지 옵저버블에 재요청을 한다거나 한계치를 설정하여 해당 횟수까지만 재요청을 진행할 수도 있다.

catchError는 클로저를 갖는다. 클로저 파라미터에 에러가 전달된 경우 옵저버블을 리턴하여 계속해서 구독자에게 다른 이벤트들을 정상적으로 방출할 수 있게 해준다.

enum MyError: Error {
    case error
}

let subject = PublishSubject<Int>()
let recovery = PublishSubject<Int>()

subject
    .catch{ _ in recovery } // 에러 catch
    .subscribe { print($0) }
    .disposed(by: bag)

subject.onError(MyError.error)
recovery.onNext(3)
recovery.onCompleted()

트레일링 클로저

위의 코드에서 catch{ a in recovery }는 아래 코드와 같다.

subject
    .catch{ _ in // 와일드카드 패턴으로 작성된 파라미터는 Error타입
        return recovery
    }

소스 옵저버블에서 에러를 한번 리턴하게 되면 이후로 더 이상 이벤트 방출이 불가능하다.

catchAndReturn 연산자는 에러 발생시 기본값을 리턴하며 해당 값은 소스 옵저버블의 타입을 그대로 따른다.

enum MyError: Error {
    case error
}

let subject = PublishSubject<Int>()

subject
    .catchAndReturn(3)
    .subscribe { print($0) } // next(3) 이후 completed
    .disposed(by: bag)

subject.onError(MyError.error)

# retry

retry연산자는 빈 파라미터를 갖고 생성하거나, maxAttemptCount값을 지정하여 생성할 수 있다. 해당 파라미터는 retry 최대 시도횟수를 지정한다.

이 값은 최대 시도횟수 + 1로 지정하여 전달해야 의도대로 동작한다고 설명에 나와있다.

var attempts = 1 // 최대 시도 횟수를 변수로 관리

let source = Observable<Int>.create { observer in
    let currentAttempts = attempts
    print("#\(currentAttempts) START")

    if attempts < 10 { // 10회 이하인 경우 에러 방출 후 retry
        observer.onError(MyError.error)
        attempts += 1
    }

    observer.onNext(1)
    observer.onNext(2)
    observer.onCompleted()

    return Disposables.create {
        print("#\(currentAttempts) END")
    }
}

source
    .retry(5) // 최대 4회까지만 retry
    .subscribe { print($0) }
    .disposed(by: bag)

위의 코드는 최종적으로 에러를 방출하며 종료된다.

만약 재시도 요청을 사용자의 버튼 클릭과 같은 인터랙션 감지 시에만 진행하고 싶을때는 retryWhen 연산자를 사용한다.

let trigger = PublishSubject<Void>()

source
    .retry(when: { _ in trigger })
    .subscribe { print($0) }
    .disposed(by: bag)

trigger.onNext(()) // 1회 재시도

트리거 옵저버블을 retry 연산자의 when 클로저에서 리턴해준다. 이후 트리거 옵저버블에서 next이벤트를 전달하면 소스 옵저버블에서 새로운 구독이 시작된다.

retry를 통한 재요청은 옵저버블이 리턴되는 형태이므로, 새로운 구독이 시작된다고 표현한다.