목차
RxJava error: 값 대신 실패 신호 보내기
Flowable.error(cause)는 구독되면 onNext 없이 onError(cause)를 보낸다. 실패는 종료 신호이므로 같은 구독에서 그 뒤의 onComplete나 새 값은 오지 않는다. empty()는 정상적으로 값이 없는 상태, never()는 끝나지 않은 상태다. 세 연산자가 화면에 같은 “값 없음”처럼 보이더라도 후속 처리에는 큰 차이가 난다.

그림의 X가 오류다. X 이후 원이나 완료선이 없다는 것은 이 소스가 더 이상 값을 전달하지 않는다는 뜻이다.
오류 신호의 시점
import io.reactivex.Flowable;
public class ErrorExample {
public static void main(String[] args) {
Flowable<String> source = Flowable.error(
new IllegalArgumentException("잘못된 요청")
);
System.out.println("구독 전");
source.subscribe(
System.out::println,
error -> System.out.println("오류: " + error.getMessage()),
() -> System.out.println("완료")
);
System.out.println("구독 후");
}
}
별도 스케줄러가 없는 이 예제의 예상 순서는 구독 전 → 오류: 잘못된 요청 → 구독 후다. 예외 객체는 Flowable.error(...)를 호출할 때 만들어졌지만 오류 신호는 구독 시 전달된다. 구독마다 새 예외 객체가 필요하면 해당 버전의 error(() -> new IllegalArgumentException(...)) 오버로드를 쓴다. 콜백에서 오류를 받지 않으면 전역 오류 처리 경로에 맡기게 되어 장애를 놓치기 쉽다.
앞의 값을 살리고 뒤는 멈춘다
Flowable.concat(
Flowable.just("A"),
Flowable.<String>error(new IllegalStateException("중단")),
Flowable.just("B")
).subscribe(
System.out::println,
error -> System.out.println(error.getMessage())
);
// A, 중단. B 소스는 구독되지 않음
concat은 이전 소스가 정상 완료해야 다음으로 이동한다. A 뒤의 오류 소스가 실패로 종료하므로 B에 도달하지 못한다. 반면 Flowable.empty()는 정상 완료하므로 다음 소스로 넘어간다. 어떤 입력이 비어 있어 무시해도 되는지와 전체 작업을 중단해야 하는지는 업무 규칙으로 결정한다.
Flowable.<String>error(new IllegalStateException("원본 실패"))
.onErrorResumeNext(Flowable.just("대체 결과"))
.subscribe(System.out::println); // 대체 결과
복구 연산자를 오류 뒤에 붙이면 실패를 대체 스트림으로 바꾸고 전체가 정상 완료할 수 있다. 단, 대체값이 실제 결과와 구별되지 않으면 실패를 숨기는 문제가 생긴다. 일시적 외부 서비스 장애에 캐시를 돌려줄지, 입력 검증 오류를 호출자에게 그대로 알릴지 분리한다. 구독을 취소한 뒤에는 더 이상 오류와 대체값을 기다리지 않으며, 비동기 소스라면 해당 작업의 취소 가능 여부도 확인한다.
