본문 바로가기

Language/RxJava

[RxJava] 7장. 디버깅과 예외 처리 2 - 예외 처리

728x90

예외처리

RxJava에서는 try-catch 문을 사용해서 예외처리를 할 수 없다.

onError 이벤트는 데이터 흐름이 바로 중단되므로, Out Of Memory 같은 중대한 에러가 발생했을 때만 활용.

 

onErrorReturn() 함수

에러가 발생했을 때 내가 원하는 데이터로 대체하는 함수.

onError 이벤트는 발생하지 않는다.

 

String[] val = {"1", "2", "#"};

Observable<Integer> source = Observable.fromArray(val)
	.map(data -> Integer.parseInt(data))
	.onErrorReturn(e -> {
		if(e instanceof NumberFormatException) {
			e.printStackTrace();
		}
		return -1;
	}).subscribe(Log::i);

 

val 배열의 3번째 값은 숫자가 아니기 때문에 에러가 발생하며, onErrorReturn에 의해 -1의 값이 리턴된다.

따라서, 1, 2, # 이 아닌 1, 2, -1 의 값이 출력된다.

 

onErrorReturn()의 장점

  • 예외 발생이 예상되는 부분을 선언적으로 처리할 수 있다.
  • Observable을 생성하는 측과 구독하는 측이 서로 다를 때, 구독자가 선언된 예외 상황을 보고 그에 맞는 처리가 가능하다.

onErrorReturnItme() 함수

onErrorReturn과 동일하지만, Throwable 객체를 인자로 사용하지 않기 때문에 코드의 가독성이 높아지고 간결해진다.

단, 예외의 종류는 확인할 수 없다.

 

onErrorResumeNext() 함수

에러가 발생했을 때 내가 원하는 Observable로 대체하는 방법.

Observable로 대체한다는 것은, 에러 발생 시 데이터를 교체하는 것뿐만 아니라 추가 작업을 진행할 수 있음을 의미한다.

 

String[] val = {"1", "2", "#"};

Observable<String> onParseError = Observable.just("Test").subscribe(Log::i);

Observable<Integer> source = Observable.fromArray(val)
	.map(Integer::parseInt)
	.onErrorResumNext(onParseError)
	.subscribe(Log::i);

 

retry() 함수

예외 처리는 재시도로 처리할 수도 있다.

retry() 함수는 Observable에서 onError 이벤트가 발생하면 다시 subscribe() 함수를 호출하여 재구독 하게 되어있다.

다양한 오버로딩이 제공되며, 인자는 재시도 횟수를 지정하거나, 어떤 조건에서 재시도 할 것인지 판단한다.

 

CommUtils.exampleStart(); // 시간 표시하는 용도

String url = "https://api.github.com/heegs";

Observable<String> source = Observable.just(url)
	.map(OKHttpHelper::getT)
	.retry(5)
	.onErrorReturn(e -> CommonUtils.ERROR_CODE);
    
source.subscribe(data -> Log.it("result : " + data);


// 출력 결과
main | 501 | error = api.github.com
main | 502 | error = api.github.com
main | 502 | error = api.github.com
main | 503 | error = api.github.com
main | 503 | error = api.github.com
main | 503 | error = api.github.com
main | 503 | error = result : -500

 

출력 결과를 보면 5회의 retry 후에 최종 요청이 실패 처리된 것을 볼 수 있다.

여기서, 시간을 보면 retry가 최대 1ms의 차이를 두고 연속적으로 진행이 되었는데 이런식으로 재시도를 하는 것은 사실상 처리에 큰 도움이 되지 않는다.

 

따라서, 보통은 재시도할 때 delay를 주고 재 시도를 수행한다.

 

CommUtils.exampleStart(); // 시간 표시하는 용도

String url = "https://api.github.com/heegs";

Observable<String> source = Observable.just(url)
	.map(OKHttpHelper::getT)
	.retry( (retryCnt, e) -> {
		Log.d("retryCount = " + retryCnt);
		CommonUtils.sleep(1000);
		
		return retryCnt < 5 ? true : false;
	)
	.onErrorReturn(e -> CommonUtils.ERROR_CODE);
    
source.subscribe(data -> Log.it("result : " + data);


// 출력 결과

main | 500 | error = api.github.com
main | error = retryCnt = 1
main | 1500 | error = api.github.com
main | error = retryCnt = 2
main | 2500 | error = api.github.com
main | error = retryCnt = 3
main | 3500 | error = api.github.com
main | error = retryCnt = 4
main | 4500 | error = api.github.com
main | error = retryCnt = 5
main | 5500 | value = result : -500

 

delay를 1000ms로 주었기 때문에, 출력된 시간을 보면 1000ms 만큼 차이가 발생함을 볼 수 있다.

 

retryUntil() 함수

특정 조건이 충족될 때 까지만 재시도하는 함수.

retry는 재시도를 지속할 조건이 없을 때 재시도를 중단한다면, retryUntil은 재시도를 중단할 조건이 발생할 때 까지 계속 재시도한다.

 

CommUtils.exampleStart(); // 시간 표시하는 용도

String url = "https://api.github.com/heegs";

Observable<String> source = Observable.just(url)
	.map(OKHttpHelper::getT)
	.subscribeOn(Schedulers.io())
	.retryUntil(() -> {
		// 네트워크가 사용 가능한지 체크
		if(CommonUtils.isNetworkAvailable()){ // 사용 가능하면 true, 불가능하면 false
			return true; // true는 중지
		}
        
		CommonUtils.sleep(1000);
		return false; // false는 계속 진행
	});

source.subscribe(Log::i);

// io 스케줄러에서 실행되기 때문에 sleep 함수가 필요
CommonUtils.sleep(2000);

 

retryUntil의 조건으로 CommonUtils.isNetworkAvailable()을 사용하여 네트워크가 사용 가능한 상태일 때 까지 재시도하는 함수이다.

재시도는 1000ms마다 이루어지게 되어있다.

 

retryWhen() 함수

retry 관련 함수 중 가장 복잡한 함수.

재시도 조건을 동적으로 설정해야 하는 경우에 활용.

다양한 예제를 찾아보고 이해하는 과정이 필요.

728x90