RxJava:调用onError而不完成/取消订阅

时间:2015-02-06 13:40:17

标签: error-handling rx-java

我有以下代码(*),它使用递归调用提供的observable的调度程序实现轮询。

(*)灵感来自https://github.com/ReactiveX/RxJava/issues/448

当我仅将onNext事件传递给订阅者时,此操作正常。但是,当我将onError事件传递给订阅者时,将调用unsubscribe事件,这反过来会导致调度程序被终止。

我也希望将错误传递给订阅者。任何想法如何实现?

public Observable<Status> observe() {
    return Observable.create(new PollingSubscriberAction<>(service.getStatusObservable(), 5, TimeUnit.SECONDS));
}

private class PollingSubscriberAction<T> implements Observable.OnSubscribe<T> {
    private Subscription subscription;
    private Subscription innerSubscription;
    private Scheduler.Worker worker = Schedulers.newThread().createWorker();

    private Observable<T> observable;
    private long delayTime;
    private TimeUnit unit;

    public PollingSubscriberAction(final Observable<T> observable, long delayTime, TimeUnit unit) {
        this.observable = observable;
        this.delayTime = delayTime;
        this.unit = unit;
    }

    @Override
    public void call(final Subscriber<? super T> subscriber) {
        subscription = worker.schedule(new Action0() {
            @Override
            public void call() {
                schedule(subscriber, true);
            }
        });

        subscriber.add(Subscriptions.create(new Action0() {
            @Override
            public void call() {
                subscription.unsubscribe();
                if (innerSubscription != null) {
                    innerSubscription.unsubscribe();
                }
            }
        }));
    }

    private void schedule(final Subscriber<? super T> subscriber, boolean immediately) {
        long delayTime = immediately ? 0 : this.delayTime;
        subscription = worker.schedule(createInnerAction(subscriber), delayTime, unit);
    }

    private Action0 createInnerAction(final Subscriber<? super T> subscriber) {
        return new Action0() {
            @Override
            public void call() {
                innerSubscription = observable.subscribe(new Observer<T>() {
                    @Override
                    public void onCompleted() {
                        schedule(subscriber, false);
                    }

                    @Override
                    public void onError(Throwable e) {
                        // Doesn't work.
                        // subscriber.onError(e);
                        schedule(subscriber, false);
                    }

                    @Override
                    public void onNext(T t) {
                        subscriber.onNext(t);
                    }
                });
            }
        };
    }
}

3 个答案:

答案 0 :(得分:5)

onError和onCompleted都是终止事件,这意味着你的Observable在任何事件发生后都不会发出任何新事件。为了吞下/处理错误案例,请参阅错误操作符 - https://github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators。此外,为了实现轮询,您可以利用这一点 - http://reactivex.io/documentation/operators/interval.html

答案 1 :(得分:3)

所以我已经玩了一段时间了,而且我认为你可以按照自己的方式行事。调用onErroronCompleted会终止该流,在done包装中翻转SafeSubscriber标记,并且没有办法重置它。

我可以看到2个可用选项 - 我认为它们都不是特别优雅,但可以使用。

1 - UnsafeSubscribe。可能不是最好的主意,但它有效,因为它不是将Subscriber包裹在SafeSubscriber中,而是直接调用它。最好阅读Javadoc,看看这对你是否合适。或者,如果您喜欢冒险,请编写您自己的SafeSubscriber,您可以在其中重置完成标记或类似标记。在您的示例中,请调用:

observe.unsafeSubscribe(...)

2 - 实施与this example类似的内容。我很欣赏它在C#中,但它应该是可读的。简单地说 - 你想创建一个Pair<T, Exception>课程,然后打电话给onError而不是打电话给onNext,并设置你的一对的异常面。您的订阅者必须更加聪明才能检查对中的每一方,并且您可能需要在源ObservableObservable<Pair<T, Exception>>之间进行一些数据转换,但我可以&#39;看看为什么它不会起作用。

如果有人有任何方式,我真的很想看到另一种做法。

希望这有帮助,

威尔

答案 2 :(得分:3)

正如@Will所说,你不能直接调用onError而不终止observable。由于您只能调用onNext,因此我决定使用Notification将值和throwable包装在一个对象中。

import rx.*;
import rx.functions.Action0;
import rx.schedulers.Schedulers;
import rx.subscriptions.Subscriptions;

import java.util.concurrent.TimeUnit;

public class PollingObservable {
    public static <T> Observable<Notification<T>> create(Observable<T> observable, long delayTime, TimeUnit unit) {
        return Observable.create(new OnSubscribePolling<>(observable, delayTime, unit));
    }

    private static class OnSubscribePolling<T> implements Observable.OnSubscribe<Notification<T>> {
        private Subscription subscription;
        private Subscription innerSubscription;
        private Scheduler.Worker worker = Schedulers.newThread().createWorker();

        private Observable<T> observable;
        private long delayTime;
        private TimeUnit unit;

        private boolean isUnsubscribed = false;

        public OnSubscribePolling(final Observable<T> observable, long delayTime, TimeUnit unit) {
            this.observable = observable;
            this.delayTime = delayTime;
            this.unit = unit;
        }

        @Override
        public void call(final Subscriber<? super Notification<T>> subscriber) {
            subscription = worker.schedule(new Action0() {
                @Override
                public void call() {
                    schedule(subscriber, true);
                }
            });

            subscriber.onStart();
            subscriber.add(Subscriptions.create(new Action0() {
                @Override
                public void call() {
                    isUnsubscribed = true;

                    subscription.unsubscribe();
                    if (innerSubscription != null) {
                        innerSubscription.unsubscribe();
                    }
                }
            }));
        }

        private void schedule(final Subscriber<? super Notification<T>> subscriber, boolean immediately) {
            if (isUnsubscribed) {
                return;
            }

            long delayTime = immediately ? 0 : this.delayTime;
            subscription = worker.schedule(createInnerAction(subscriber), delayTime, unit);
        }

        private Action0 createInnerAction(final Subscriber<? super Notification<T>> subscriber) {
            return new Action0() {
                @Override
                public void call() {
                    innerSubscription = observable.subscribe(new Observer<T>() {
                        @Override
                        public void onCompleted() {
                            schedule(subscriber, false);
                        }

                        @Override
                        public void onError(Throwable e) {
                            subscriber.onNext(Notification.<T>createOnError(e));
                            schedule(subscriber, false);
                        }

                        @Override
                        public void onNext(T t) {
                            subscriber.onNext(Notification.createOnNext(t));
                        }
                    });
                }
            };
        }
    }
}

要使用此功能,您可以直接使用通知:

PollingObservable.create(service.getStatus(), 5, TimeUnit.SECONDS)
    .subscribe(new Action1<Notification<Status>>() {
        @Override
        public void call(Notification<Status> notification) {
            switch (notification.getKind()) {
                case OnNext:
                    Status status = notification.getValue();
                    // handle onNext event
                    break;
                case OnError:
                    Throwable throwable = notification.getThrowable();
                    // handle onError event
                    break;
            }
        }
    });

或者您可以使用通知上的accept方法来使用常规Observable:

PollingObservable.create(service.getStatus(), 5, TimeUnit.SECONDS)
        .subscribe(new Action1<Notification<Status>>() {
            @Override
            public void call(Notification<Status> notification) {
               notification.accept(statusObserver);
            }
        });

Observer<Status> statusObserver = new Observer<Status>() {
    // ...
}

更新2015-02-24

似乎轮询观察结果有时无法正常工作,因为内部可观察者即使在取消订阅后也会调用onCompleteonError,从而重新安排自己。我添加了isUnsubscribed标志以防止这种情况发生。