Error Handling
catch, catchAndReturn, retry
상황 : 옵저버블이 네트워크 요청을 처리하고, 구독자가 ui를 업데이트한다.
에러이벤트가 전달되면 구독이 종료돼서 ui를 업데이트 하는 코드가 실행되지 않음.
Rx는 이 경우,
1. 첫 번째 방법으로, catch 연산자를 통해 next / completed event는 그대로 전달하지만error event가 전달되면 새로운 옵저버블로 바꿔서 전달한다.
특히, 네트워크 요청 코드에서 많이 사용한다.
catch 연산자는 클로저를 파라미터로 받고,
Error 이벤트는 클로저의 파라미터로 전달되고, 클로저는 새로운 Observable을 반환한다.
catch 연산자는 클로저가 반환하는 Observable과 같은 Observable 값을 반환한다.
Error event가 전달되는 경우에 클로저가 반환하는 Observable로 교체해서 반환하는 방식이다.
코드로 보면,
let subject = PublishSubject<Int>()
let recovery = PublishSubject<Int>()
subject
.catch { _ in
// catch 연산자를 통해서 기존 subject를 recovery 서브젝트로 교체함
recovery
}
.subscribe { print($0) }
.disposed(by: disposeBag)
subject.onError(MyError.error)
subject.onNext(123) // error로 구독이 종료돼서 새로운 이벤트는 전달되지 않음
subject.onNext(11) // 전달되지 않음
recovery.onNext(22) // next(22) 전달됨
recovery.onCompleted()
catch를 통해 subject 서브젝트를 recovery 서브젝트로 교체했음.
즉, catch 연산자는 error 이벤트를 방출하는 기존 옵저버블을 새로운 옵저버블로 교체하는 방식으로 에러를 핸들링한다.
클로저를 통해 에러처리를 자유롭게 할 수 있다는 장점이 있다.
2. catchAndReturn 연산자는 에러가 발생했을 때 사용할 기본값이 있는 경우에 사용한다.
이 옵저버블에서 기본값을 보내거나 로컬 캐쉬를 방출하도록 해결한다.
옵저버블과 같은 타입을 기본값으로 받는다.
근데 에러에 종류와 상관없이 동일한 값을 리턴하는 단점이 있다.
코드는 에러가 발생시 -1을 전달한다.
let mySubject = PublishSubject<Int>()
mySubject
.catchAndReturn(-1)
.subscribe { print($0) }
.disposed(by: disposeBag)
mySubject.onError(MyError.error) // next(-1)
3. retry 연산자를 통해 옵저버블을 다시 구독한다.
에러가 발생하지 않을 때까지 무한정 재시도하거나 재시도 횟수를 제한하는 방법으로 해결한다.
옵저버블에서 에러가 발생하면 새로운 구독을 다시 시작하는 방식
1). 파라미터 없는 retry
옵저버블이 정상적으로 완료될 때까지 계속해서 재시도
리소스 낭비 무한루프가 발생할 수도 있음 - 강종이슈가 있을지도...
2). 파라미터 있는 retry
재시도 횟수를 설정할 수 있음
parameter maxAttemptCount: Maximum number of times to repeat the sequence.
If you encounter an error and want it to retry once, then you must use `retry(2)`
설명처럼 재시도 횟수를 1번 설정하려면 retry(2)라고 해줘야 함즉, 1씩 추가해서 써줘야 하는 것
첫 번째 시도는 재시도가 아니라 정확히 말하자면, 재시도 횟수는 1번을 빼주고 카운팅해줘야 함
만약에 재시도 횟수 (7) 내에 에러가 해결되면 그 다음 next 이벤트와 complete 이벤트가 방출되어 전달된다.
var attempts = 1
let source = Observable<Int>.create { observer in
let currentAttempts = attempts
print("#START => \(currentAttempts)")
if attempts < 3 {
observer.onError(MyError.error)
attempts += 1
}
observer.onNext(1)
observer.onNext(2)
observer.onCompleted()
return Disposables.create {
print("#END => \(currentAttempts)")
}
}
source
.retry(7)
.subscribe {
print($0)
}
.disposed(by: disposeBag)
// 출력결과
#START => 1
#END => 1
#START => 2
#END => 2
#START => 3
next(1)
next(2)
completed
#END => 3
이 경우 2번의 재시도라고 보면 됨
3). retryWhen
위 경우들처럼 바로 재시도를 하는 게 아닌 사용자가 버튼을 탭하는 시점과 같이 특정 경우에 재시도 하고 싶다면
let trigger = PublishSubject<Void>()
source
.retry { _ in trigger }
.subscribe {
print($0)
}
.disposed(by: disposeBag)
trigger.onNext(())
바로 재시도 하지 않고 trigger observable이 next event를 방출하기 전까지 대기하다가
전달하면 재시도를 함
'⭐️ 개발 > Rx' 카테고리의 다른 글
7. Traits - ControlProperty, ControlEvent, Driver (0) | 2023.01.16 |
---|---|
6. Binder (0) | 2023.01.16 |
[Rx] Input/Output 패턴 적용하기 - 비즈니스 로직 분리!! (4) | 2022.11.02 |
[Rx] TIL - Rx 개념 재복습 및 총정리 (0) | 2022.10.31 |
[Rx] Observable, Observer, Disposable, Subject, Relay (4) | 2022.10.26 |